Springboot-security(权限控制)

  1. 核心:
    • 认证
    • 授权
  2. 自定义配置相关类:WebSecurityConfigAdapter
  3. 相关方法:
    • config(AuthenticationMangerBuilder auth):认证
    • configure(HttpSecurity http):授权
    • configure(WebSecurity web):资源放行
    • passwordEncoder passwordEncoder():密码加密
  4. 登录校验流程
    1. 前端携带用户名密码访问登录接口
    2. 服务器端在数据库中对账号和密码进行校验
    3. 如果正确,使用用户名或id生成jwt,并将jwt响应给前端
    4. 登录后访问其他请求,需要在请求头中携带token
    5. 根据请求头中的token获取用户ID,如果有权限则允许访问相关资源
    6. 访问目标资源,返回给前端

- 基础用法

  1. 引入依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.7.3</version>
    </dependency>
  2. 添加测试接口

    1
    2
    3
    4
    5
    6
    7
    8
    @RestController
    public class HelloController(){

    @GetMapping("/test")
    public String hello(){
    return "Hello";
    }
    }
  3. 启动项目测试访问test接口,接口会自定跳到security提供的默认登录页面,默认用户名为user,密码为控制台随机生成

  4. 配置用户名和密码(由开发人员指定)

    1
    2
    3
    spring.security.username=root
    spring.security.password=1234
    spring.security.roles=admin

- 基于内存的认证

  1. 自定义类继承WebSecurityConfigurerAdapter实现config(AuthenticationMangerBuilder auth)`方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Configuration
    public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean//密码加密
    passwordEncoder passwordEncoder(){
    return NoOpPasswordEncoder.getInstance();
    }

    @Override//认证
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
    .withUser("admin").password("1234").roles("ADMIN")
    .and()
    .withUser("user").password("user").roles("USER")
    .and()
    .withUser("dba").password("1234").roles("DBA");
    }
    }

- HttpSecurity授权

  1. 自定义类继承WebSecurityConfigAdapter实现config(HttpSecurity http)方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    @Configuration
    public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean//密码加密
    passwordEncoder passwordEncoder(){
    return NoOpPasswordEncoder.getInstance();
    }

    @Override//认证
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
    .withUser("admin").password("1234").roles("ADMIN")
    .and()
    .withUser("user").password("user").roles("USER")
    .and()
    .withUser("dba").password("1234").roles("DBA");
    }

    @Override//授权
    protected void configure(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests()
    .antMatchers("/admin/**")
    .hasRole("ADMIN")
    .antMatchers("/user/**")
    .hasRole("USER")
    .antMatchers("/db/**")
    .hasRole("DBA")
    .antMatchers("/error/**")
    .permitAll()
    .anyRequest()
    .authenticated()
    .and()
    .csrf()
    .disable();
    }
    }

- 自定义登录页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {

@Bean//密码加密
passwordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}

@Override//放行static下登录页面相关的静态资源
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/js/**","/img/**","/css/**");
}

@Override//认证
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("1234").roles("ADMIN")
.and()
.withUser("user").password("user").roles("USER")
.and()
.withUser("dba").password("1234").roles("DBA");
}

@Override//授权
protected void configure(HttpSecurity http) throws Exception {
http.authorizeHttpRequests()
.antMatchers("/admin/**")
.hasRole("ADMIN")
.antMatchers("/user/**")
.hasRole("USER")
.antMatchers("/db/**")
.hasRole("DBA")
.antMatchers("/error/**")
.permitAll()//对所有用户开放
.anyRequest()
.authenticated()//用户访问除前面定义的URL模式外,访问其他URL必须登录
.and()
.formLogin()//配置表单登录
.loginPage("/login.html")
.loginProcessingUrl("/login")//方便ajax或移动端调用登录接口
.successHandler(new AuthenticationSuccessHandler() {

public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
Object principal= authentication.getPrincipal();
response.setContentType("application/json;charset=utf-8");
PrintWriter out=response.getWriter();
response.setStatus(200);
Map<String,Object> map=new HashMap<String, Object>();
map.put("status",200);
map.put("msg",principal);
ObjectMapper om=new ObjectMapper();
out.write(om.writeValueAsString(map));
out.flush();
out.close();
}
})
.failureHandler(new AuthenticationFailureHandler() {
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
PrintWriter out=response.getWriter();
response.setStatus(200);
Map<String,Object> map=new HashMap<String, Object>();
map.put("status",401);
if (exception instanceof LockedException){
map.put("msg","账户被锁定");
}else if(exception instanceof BadCredentialsException){
map.put("msg","账户名或密码输入错误,登陆失败");
}else if(exception instanceof DisabledException){
map.put("msg","账户被禁用");
}else if (exception instanceof AccountExpiredException){
map.put("msg","账户已过期");
}else if (exception instanceof CredentialsContainer){
map.put("msg","密码已过期");
}else {
map.put("msg","登录失败");
}

ObjectMapper om=new ObjectMapper();
out.write(om.writeValueAsString(map));
out.flush();
out.close();
}
}
.permitAll()//和登录相关的接口都不需要认证
.and()
.csrf()
.disable();
}


}

- SpringSecurity完整流程

SpringSecurity的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器

  • BasicAuthenticationFilter实现的是HttpBasic模式的登录认证
  • UsernamePasswordAuthenticationFilter实现用户名密码的登录认证
  • RememberMeAuthenticationFilter实现登录认证的“记住我”的功能
  • SmsCodeAuthenticationFilter实现短信验证码登录认证
  • SocialAuthenticationFilter实现社交媒体方式登录认证的处理
  • Oauth2AuthenticationProcessingFilter和Oauth2ClientAuthenticationProcessingFilter实现Oauth2的鉴权方式

- 注销登陆配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
.and()
.logout() //开启注销配置
.logoutUrl("/logout") //注销接口为"/logout",默认也是"/logout"
.clearAuthentication(true) //清除身份信息,默认true
.invalidateHttpSession(true) //是否使session失效,默认true
.addLogoutHandler(new LogoutHandler(){ //可在该方法中完成一些数据清楚工作,如Cookie的清除
public void logout(HttpServletRequest request,
HttpServletResponse response,
Authentication auth){

}
})
.logoutSuccessHandler(new LogoutSuccessHandler(){ //注销成功的业务逻辑,返回json信息或跳转登录页面等
public void onLogoutSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication auth) throws IOException{
response.sendRedirect("/login_Page");
}
})
.and()
.rememberMe() //开启记住我功能
.rememberMeParameter("remember")
.and()
.csrf()
.disable();

- 多个HttpSecurity

当业务复杂,开发者可以配置多个HttpSecurity,实现对WebSecurityConfigurerAdapter的多次扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Configuration
public class MultiHttpSecurityConfig{

@Bean
PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}

@Autowired
protected void config(AuthenticationManagerBuilder auth) throws Exception{
auth.inMemoryAuthentication()
.withUser("admin").password("123").roles("ADMIN","USER")
.and()
.withUser("sang").password("1234").roles("USER")
}

@Configuration
@Order(1) //主要用于处理/admin/**模式的url
public static class AdminSecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception{
http.authorizeRequests()
.antMatcher("/admin/**")
.anyRequest().hasRole("ADMIN");
}
}

@Configuration//处理其他模式的注解
public static class OtherSecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception{
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginProcessingUrl("/login")
.permitAll()
.and()
.csrf()
.disable();
}
}
}

==代码解释==:配置多个HttpSecurity时,MultiHttpSecurityConfig不需要继承WebSecurityConfigurerAdapter,只用在其中创建多个静态内部类继承WebSecurityConfigurerAdapter,静态内部类上添加@Configuration注解和@Order(1)注解,数字越小优先级越大

- 密码加密

- 配置基于内存的密码加密

1
2
3
4
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder(10);//密匙的迭代次数为10
}

- 配置基于数据库的密码加密

1
2
3
4
5
6
7
8
@Service 
public class RegService{
public int reg(String name,String password){
BCryptPasswordEncoder encoder=new BCryptPasswordEncoder(10);
String encodePassword=encoder.encode(password);
return saveToDb(username,encodePasswod);
}
}

- 方法安全

  1. 开启基于注解的安全配置:@EnableGlobalMethodSecurity

  2. ```
    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled=true,securedEnabled=true)
    public class WebSecurityConfig{

    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27

    3. 代码解释

    - `prePostEnabled=true`会解锁`@PreAuthorize`(方法前执行)和`@PostAuthorize`(方法后执行)两个注解
    - `securedEnabled=true`会解锁`@Secured`注解

    4. 代码测试

    ```java
    @Service
    public class MethodService{

    @Secured("ROLE_ADMIN")
    public String admin(){
    return "hello admin";
    }

    @PreAuthorize("hasRole('ADMIN') and hasRole('DBA')")
    public String dba(){
    return "hello dba";
    }

    @PreAuthorize("hasAnyRole('ADMIN','DBA','USER')")
    public String user(){
    return "user";
    }
    }

    代码解释:

    • @Secured(“ROLE_ADMIN”)表示访问该方法需要ADMIN角色,角色前一定要添加ROLE_
    • @PreAuthorize(“hasRole(‘ADMIN’) and hasRole(‘DBA’)”),访问该方法同时需要ADMIN和DBA角色
    • @PreAuthorize(“hasAnyRole(‘ADMIN’,’DBA’,’USER’)”),访问该方法需要三种角色中任意一个角色
  3. Controller中注入Service,调用方法测试

- 基于数据库的认证

  1. 设计数据表:角色表,用户表,用户角色关联表

    • user(id,username,password,enabled,locked)
    • role(id,name,nameZh)
    • user_role(id,uid,rid)
  2. 创建项目SpringBoot项目

  3. 配置数据库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #数据库配置
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url="jdbc:mysql://localhost:3306/spring_security?serverTimezone=GMT%2B8"
    spring.datasource.data-username=root
    spring.datasource.password=123456
    spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

    #开启日志,方便调试
    mybatis-plus.configuration.log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    mybatis-plus.mapper-locations=classpath:com/security/demo/mapper/xml/*.xml

    #返回json的全局时间格式
    spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
    spring.jackson.time-zone=GMT+8
  4. 创建实体类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    @Data
    @EqualsAndHashCode(callSuper = false)
    @Accessors(chain = true)
    @ApiModel(value="User对象", description="")
    public class User implements Serializable, UserDetails {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "主键")
    @TableId(value = "id", type = IdType.ID_WORKER_STR)
    private String id;

    @ApiModelProperty(value = "账户名")
    private String username;

    @ApiModelProperty(value = "密码")
    private String password;

    @ApiModelProperty(value = "是否登录")
    private Boolean enabled;

    @ApiModelProperty(value = "是否锁定")
    private Boolean locked;

    @TableField(fill = FieldFill.INSERT)
    @ApiModelProperty(value = "创建时间")
    private Date gmtCreate;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    @ApiModelProperty(value = "修改时间")
    private Date gmtModified;

    private List<Role> roles;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
    List<SimpleGrantedAuthority> authorities=new ArrayList<SimpleGrantedAuthority>();
    for (Role role:roles) {
    authorities.add(new SimpleGrantedAuthority(role.getName()));
    }
    return authorities;
    }

    @Override
    public boolean isAccountNonExpired() {
    return true;
    }

    @Override
    public boolean isAccountNonLocked() {
    return !locked;
    }

    @Override
    public boolean isCredentialsNonExpired() {
    return true;
    }

    @Override
    public boolean isEnabled() {
    return enabled;
    }
    }
  5. 创建UserService实现UserDeatailsService接口中的loadUserByUsername(String name)

    1
    2
    3
    4
    5
    <!--UserMapper.java-->
    public interface UserMapper extends BaseMapper<User> {
    User loadUserByUserName(String username);
    List<Role> getUserRolesByUid(String uid);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!--UserMapper.xml-->
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.security.demo.mapper.UserMapper">

    <select id="loadUserByUserName" resultType="com.security.demo.entity.User">
    select * from user where username=#{username}
    </select>

    <select id="getUserRolesByUid" resultType="com.security.demo.entity.Role">
    select * from role r, user_role ur where r.id=ur.rid and ur.uid=#{uid}
    </select>
    </mapper>
    1
    2
    3
    4
    5
    public interface UserService extends IService<User>, UserDetailsService {

    @Override
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    public UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    User user=userMapper.loadUserByUserName(username);
    if(user==null){
    throw new UsernameNotFoundException("账户不存在");
    }
    user.setRoles(userMapper.getUserRolesByUid(user.getId()));

    return user;
    }
    }
  6. 配置Spring Security

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    @Configuration
    public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    Uservice uservice;

    @Bean//密码加密
    passwordEncoder passwordEncoder(){
    return new BCryptPasswordEncoder();
    }

    @Override//认证
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userService);
    }

    @Override//授权
    protected void configure(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests()
    .antMatchers("/admin/**")
    .hasRole("ADMIN")
    .antMatchers("/user/**")
    .hasRole("USER")
    .antMatchers("/db/**")
    .hasRole("DBA")
    .antMatchers("/error/**")
    .permitAll()
    .anyRequest()
    .authenticated()
    .and()
    .csrf()
    .disable();
    }
    }

jquery Mobile