Spring Security教程 第一弹 初识spring security

it2023-05-06  71

 

写在前面的话

更多Spring与微服务相关的教程请戳这里 火力全开系列 Spring与微服务教程合集 持续更新

 

1、概述

核心概念:

认证授权:Spring Security不仅支持基于URL对Web的请求授权,还支持方法访问授权、对象访问 授权等

Spring Security已经集成的认证技术如下:

HTTP BASIC authentication headers:一个基于IETF RFC的标准HTTP Digest authentication headers:一个基于IETF RFC的标准HTTP X.509 client certificate exchange:一个基于IETF RFC的标准LDAP:一种常见的跨平台身份验证方式Form-based authentication:用于简单的用户界面需求OpenID authentication:一种去中心化的身份认证方式Authentication based on pre-established request headers:类似于 Computer Associates SiteMinder,一种用户身份验证及授权的集中式安全基础方案Jasig Central Authentication Service:单点登录方案Transparent authentication context propagation for Remote Method Invocation(RMI)and HttpInvoker:一个Spring远程调用协议Automatic "remember-me" authentication:允许在指定到期时间前自行重新登录系统Anonymous authentication:允许匿名用户使用特定的身份安全访问资源Run-as authentication:允许在一个会话中变换用户身份的机制Java Authentication and Authorization Service:JAAS,Java验证和授权APIJava EE container authentication:允许系统继续使用容器管理这种身份验证方式Kerberos:一种使用对称密钥机制,允许客户端与服务器相互确认身份的认证协议

除此之外,Spring Security还引入了一些第三方包,用于支持更多的认证技术,如JOSSO等。

如果所有这些技术都无法满足需求,则Spring Security允许我们编写自己的认证技术

 

2、入门案例

2.1、pom.xml

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>

 

2.2、application.yml

spring: security: user: #如果不配置,则默认的用户名为user,默认密码则在控制台打印 name: admin password: ok server: port: 8140 servlet: context-path: /spring-security-base

 

2.3、启动类

import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController public class SpringSecurityBaseApplication { @RequestMapping("hello") public String hello(){ return "hello!"; } public static void main(String[] args) { SpringApplication.run(SpringSecurityBaseApplication.class, args); } }

 

注意:

项目引入spring security之后,虽然不做任何配置,也会有一个Http基本认证(由于版本不同,也可能是表单认证)默认用户名是user,密码会在控制台打印;也可以在application.yml中配置用户名密码

 

3、EnableWebSecurity自动配置

3.1、开启自动配置

如果不重写configure(HttpSecurity http)方法,则默认会有以下行为:

验证所有请求允许表单验证允许Http基本认证

     

import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { super.configure(http); } }

 

3.2、HttpSecurity对象

HttpSecurity被设计为链式调用,且实际上对应的是XML中的标签

HttpSecurity提供了很多配置相关的方法,分别对应命名空间配置中的子标签<http>。

例如:

authorizeRequests()     对应    <intercept-url>formLogin()                  对应    <formlogin>httpBasic()                  对应    <http-basic>csrf()                           对应    <csrf>

调用这些方法之后,除非使用and()方法结束当前标签,上下文才会回到HttpSecurity,否则链式调用的上下文将自动进入对应标签域。

authorizeRequests()方法实际上返回了一个 URL 拦截注册器,我们可以调用它提供的anyanyRequest()、antMatchers()和regexMatchers()等方法来匹配系统的URL,并为其指定安全策略formLogin()方法和httpBasic()方法都声明了认证方式csrf()方法是Spring Security提供的跨站请求伪造防护功能,当我们继承WebSecurityConfigurerAdapter时会默认开启csrf()方法

 

4、认证和授权

4.1、基于内存

4.1.1、准备controller

准备三个controller,分别是AdminController、UserController、AppController,每个controller里面都提供hello方法

/admin/hello需要ADMIN角色才能访问,/user/hello需要USER角色才能访问,/app/hello不需要登录就能访问,为开放资源

@RestController @RequestMapping("/admin") public class AdminController { @RequestMapping("/hello") public String hello(){ return "hello,admin!"; } } @RestController @RequestMapping("/user") public class UserController { @RequestMapping("/hello") public String hello(){ return "hello,user!"; } } @RestController @RequestMapping("/app") public class AppController { @RequestMapping("/hello") public String hello(){ return "hello,app!"; } }

 

4.1.2、WebSecurityConfig

Spring Security支持各种来源的用户数据,包括内存、数据库、LDAP等。它们被抽象为一个UserDetailsService接口, 任何实现了UserDetailsService 接口的对象都可以作为认证数据源

下面配置了来源于内存的用户,并为用户分配角色,且spring security 5之后密码必须进行加密,否则登录时会报错  

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/user/**").hasRole("USER") .antMatchers("/app/**").permitAll() .anyRequest().authenticated() .and().formLogin() .and().csrf().disable(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); String pwd = encoder.encode("ok"); auth.inMemoryAuthentication().passwordEncoder(encoder) .withUser("bobo").password(pwd).roles("ADMIN") .and() .withUser("tudou").password(pwd).roles("USER"); } }

 

4.2、基于默认数据库

4.2.1、初始化数据库

除了InMemoryUserDetailsManager,Spring Security还提供另一个UserDetailsService实现类:JdbcUserDetailsManager JdbcUserDetailsManager帮助我们以JDBC的方式对接数据库和Spring Security,它设定了一个默认的数据库模型

默认数据库模型脚本在/org/springframework/security/core/userdetails/jdbc/users.ddl中

JdbcUserDetailsManager需要两个表,其中users表用来存放用户名、密码和是否可用三个信息,authorities表用来存放用户名及其权限的对应关系。

该语句是用hsqldb创建的,而MySQL不支持varchar_ignorecase这种类型,将varchar_ignorecase改为MySQL支持的varchar即可

create table users( username varchar(50) not null primary key, password varchar(500) not null, enabled boolean not null ); create table authorities ( username varchar(50) not null, authority varchar(50) not null, constraint fk_authorities_users foreign key(username) references users(username) );

 

4.2.2、pom.xml

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.15</version> </dependency> </dependencies>

 

4.2.3、application.yml

spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/spring-security-base?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai username: root password: ok

 

4.2.4、WebSecurityConfig

基于内存和基于jdbc两种方式没太大区别,只是基于jdbc会多一步设置数据源的操作

JdbcUserDetailsManager封装了操作数据库的细节,比如createUser方法实际上对应insert into user

项目启动时,会向数据库创建用户,但再次启动就会报错,因为数据库中已经有这两个用户了,解决办法是创建用户之前先判断该用户是否存在;但使用内存方式就不会出现重启问题,因为每次重启内存中的用户信息会清空

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import javax.sql.DataSource; @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/user/**").hasRole("USER") .antMatchers("/app/**").permitAll() .anyRequest().authenticated() .and().formLogin() .and().csrf().disable(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); String pwd = encoder.encode("ok"); JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> configurer = auth.jdbcAuthentication().dataSource(dataSource).passwordEncoder(encoder); if(!configurer.getUserDetailsService().userExists("bobo")){ configurer.withUser("bobo").password(pwd).roles("ADMIN"); } if(!configurer.getUserDetailsService().userExists("tudou")){ configurer.withUser("tudou").password(pwd).roles("USER"); } } }

 

4.3、基于自定义数据库

自定义数据库结构实际上是实现一个自定义的UserDetailsService

 

4.3.1、自定义数据库模型

CREATE TABLE sys_user ( id int(10) unsigned NOT NULL AUTO_INCREMENT, username varchar(255) DEFAULT NULL, roles varchar(255) DEFAULT NULL, password varchar(255) DEFAULT NULL, PRIMARY KEY (id) ); INSERT INTO sys_user(id, username, roles, password) VALUES (1, 'test1', 'ROLE_ADMIN,ROLE_USER', '789'); INSERT INTO sys_user(id, username, roles, password) VALUES (2, 'test2', 'ROLE_USER', '123');

 

4.3.2、SysUser类

SysUser只是单纯地与数据库交互,不提供身份认证和权限认证能力

public class SysUser { private Integer id; private String username; private String password; //逗号分隔的角色 private String roles; //省略get/set方法 }

 

4.3.3、SecurityUser类

SecurityUser类继承SysUser类,且实现UserDetails接口,该接口提供身份认证和权限认证能力

UserDetail接口源码:

package org.springframework.security.core.userdetails; import java.io.Serializable; import java.util.Collection; import org.springframework.security.core.GrantedAuthority; public interface UserDetails extends Serializable { //获取权限 Collection<? extends GrantedAuthority> getAuthorities(); String getPassword(); String getUsername(); //下面四个方法均返回true就行 boolean isAccountNonExpired(); boolean isAccountNonLocked(); boolean isCredentialsNonExpired(); boolean isEnabled(); }

 

SecurityUser类:

import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.util.List; public class SecurityUser extends SysUser implements UserDetails { public SecurityUser(){} public SecurityUser(SysUser sysUser){ super.setId(sysUser.getId()); super.setUsername(sysUser.getUsername()); super.setPassword(sysUser.getPassword()); super.setRoles(sysUser.getRoles()); } //spring security用于存放权限的属性 private List<GrantedAuthority> authorityList; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorityList; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } public void setAuthorityList(List<GrantedAuthority> authorityList) { this.authorityList = authorityList; } }

 

4.3.4、自定义UserDetailsService

ISysUserMapper采用mybatis实现:

import com.bobo.group.springsecuritybase.entity.SysUser; import org.apache.ibatis.annotations.Select; public interface ISysUserMapper { @Select("select * from sys_user where username=#{username}") SysUser getUserByUsername(String username); }

 

CustomUserDetailService:

import com.bobo.group.springsecuritybase.dao.ISysUserMapper; import com.bobo.group.springsecuritybase.entity.SecurityUser; import com.bobo.group.springsecuritybase.entity.SysUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.List; @Service public class CustomUserDetailService implements UserDetailsService { @Autowired private ISysUserMapper iSysUserMapper; @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { SysUser sysUser = iSysUserMapper.getUserByUsername(s); if(null == sysUser){ throw new UsernameNotFoundException("该用户不存在!"); } //将数据库形式的roles转换为UserDetails识别的权限集 //commaSeparatedStringToAuthorityList方法的实现是按逗号分隔,我们也可以借助SimpleGrantedAuthority类提供其它实现 List<GrantedAuthority> list = AuthorityUtils.commaSeparatedStringToAuthorityList(sysUser.getRoles()); SecurityUser securityUser = new SecurityUser(sysUser); securityUser.setAuthorityList(list); return securityUser; } }

 

4.3.5、WebSecurityConfig

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { /** * 提供不加密的PasswordEncoder */ @Bean public PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } @Autowired private CustomUserDetailService customUserDetailService; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/user/**").hasRole("USER") .antMatchers("/app/**").permitAll() .anyRequest().authenticated() .and().formLogin() .and().csrf().disable(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //基于自定义userDetailsService //这个一定要设置一下,否则就会报No AuthenticationProvider found的错 auth.userDetailsService(customUserDetailService); } }

 

 

 

 

最新回复(0)