当前位置:   article > 正文

3.Spring Security实现JWT token验证_spring security 验证token

spring security 验证token

目录


Spring Security专栏目录(点击进入…)



Spring Security实现JWT token验证

Spring Security是Spring提供的一个安全框架,提供认证和授权功能,最主要的是它提供了简单的使用方式,同时又有很高的灵活性,简单、灵活、强大
一般系统里关于角色方面通常有这么几张表:用户表、角色表、用户-角色表、菜单表、角色-菜单表、权限表、角色-权限表等

1.导入依赖

JWT认证的实现
(1)支持用户通过用户名和密码登录
(2)登录后通过http header返回token,每次请求,客户端需通过header将token带回,用于权限校验
(3)服务端负责token的定期刷新

<properties>
    <jjwt.version>0.9.1</jjwt.version>
    <spring-security-jwt.version>1.0.11.RELEASE</spring-security-jwt.version>
</properties>

<!-- Spring Security-->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-jwt</artifactId>
        <version>${spring-security-jwt.version}</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>${jjwt.version}</version>
    </dependency>
</dependencies>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

2.Spring Security配置类

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)  // 开启权限注解,默认是关闭的
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 自定义登录成功处理器
     */
    @Autowired
    private UserLoginSuccessHandler userLoginSuccessHandler;
    
    /**
     * 自定义登录失败处理器
     */
    @Autowired
    private UserLoginFailureHandler userLoginFailureHandler;
    /**
     * 自定义注销成功处理器
     */
    @Autowired
    private UserLogoutSuccessHandler userLogoutSuccessHandler;
    
    /**
     * 自定义暂无权限处理器
     */
    @Autowired
    private UserAuthAccessDeniedHandler userAuthAccessDeniedHandler;
    
    /**
     * 自定义未登录的处理器
     */
    @Autowired
    private UserAuthenticationEntryPointHandler userAuthenticationEntryPointHandler;
    /**
     * 自定义登录逻辑验证器
     */
    @Autowired
    private UserAuthenticationProvider userAuthenticationProvider;

    /**
     * 加密方式
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    /**
     * 注入自定义PermissionEvaluator
     */
    @Bean
    public DefaultWebSecurityExpressionHandler userSecurityExpressionHandler(){
        DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
        handler.setPermissionEvaluator(new UserPermissionEvaluator());
        return handler;
    }

    /**
     * 配置登录验证逻辑
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth){
        //启用自定义的登陆验证逻辑
        auth.authenticationProvider(userAuthenticationProvider);
    }

    /**
     * 配置security的控制逻辑
     * @Param  http 请求
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // 不进行权限验证的请求或资源(从配置文件中读取)
                .antMatchers(JWTConfig.antMatchers.split(",")).permitAll()
                // .antMatchers("/*").permitAll()
                // 其他的需要登陆后才能访问
                .anyRequest().authenticated()
                .and()
                // 配置未登录自定义处理类
                .httpBasic().authenticationEntryPoint(userAuthenticationEntryPointHandler)
                .and()
                // 配置登录地址
                .formLogin()
                .loginProcessingUrl("/login/userLogin")
                // 配置登录成功自定义处理类
                .successHandler(userLoginSuccessHandler)
                // 配置登录失败自定义处理类
                .failureHandler(userLoginFailureHandler)
                .and()
                // 配置登出地址
                .logout()
                .logoutUrl("/login/userLogout")
                // 配置用户登出自定义处理类
                .logoutSuccessHandler(userLogoutSuccessHandler)
                .and()
                // 配置没有权限自定义处理类
                .exceptionHandling().accessDeniedHandler(userAuthAccessDeniedHandler)
                .and()
                // 开启跨域
                .cors()
                .and()
                // 取消跨站请求伪造防护
                .csrf().disable();
        // 基于Token不需要session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        // 禁用缓存
        http.headers().cacheControl();
        // 添加JWT过滤器
        http.addFilter(new JWTAuthenticationTokenFilter(authenticationManager()));
    }
}
  • 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
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111

3.编写JWTConfig和application.yml增加jwt相关配置

// JWT相关配置类
@Data
@Component
@ConfigurationProperties(prefix = "jwt")
public class JwtConfig {
    /**
     * 密钥KEY
     */
    private String secret;
    /**
     * TokenKey
     */
    private String tokenHeader;
    /**
     * Token前缀字符
     */
    private String tokenPrefix;
    /**
     * 过期时间
     */
    private Integer expiration;
    /**
     * 不需要认证的接口
     */
    private String antMatchers;
    /**
     * jwt发行人
     */
    private String issuer;
    /**
     * 主体
     */
    private String subject;
    /**
     * 接受者
     */
    private String audience;
    /**
     * 有效时间
     */
    private String validTime;
}
  • 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

application.yml

# JWT配置
jwt:
  # 密匙KEY
  secret: secret
  # Header KEY
  tokenHeader: infinite-auth
  # Token前缀字符
  tokenPrefix: authorization-
  # 过期时间 单位秒 1天后过期=86400 7天后过期=604800
  expiration: 86400
  # 配置不需要认证的接口
  antMatchers: /generator/table/index
  # jwt发行人
  issuer: infinite
  # 主体
  subject: to-infinite
  # 接受者
  audience: infinite-user
  # 有效时间,分钟
  validTime: 30
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

4.编写过滤器处理类

(1)登录成功处理类(AuthenticationSuccessHandler)

@Component
public class UserLoginSuccessHandler implements AuthenticationSuccessHandler {
    /**
     * 登录成功返回结果
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication){
        // 组装JWT
        SelfUserEntity selfUserEntity =  (SelfUserEntity) authentication.getPrincipal();
        String token = JWTTokenUtil.createAccessToken(selfUserEntity);
        token = JWTConfig.tokenPrefix + token;

        // 封装返回参数
        Map<String,Object> resultData = new HashMap<>();
        resultData.put("code","200");
        resultData.put("msg", "登录成功");
        resultData.put("token",token);
        ResultUtil.responseJson(response,resultData);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

(2)登录失败处理类(AuthenticationFailureHandler)

@Component
public class UserLoginFailureHandler implements AuthenticationFailureHandler {
    /**
     * 登录失败返回结果
     */
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception){
        // 这些对于操作的处理类可以根据不同异常进行不同处理
        if (exception instanceof UsernameNotFoundException){
            System.out.println("【登录失败】"+exception.getMessage());
            ResultUtil.responseJson(response,ResultUtil.resultCode(500,"用户名不存在"));
        }
        if (exception instanceof LockedException){
            System.out.println("【登录失败】"+exception.getMessage());
            ResultUtil.responseJson(response,ResultUtil.resultCode(500,"用户被冻结"));
        }
        if (exception instanceof BadCredentialsException){
            System.out.println("【登录失败】"+exception.getMessage());
            ResultUtil.responseJson(response,ResultUtil.resultCode(500,"密码错误"));
        }
        ResultUtil.responseJson(response,ResultUtil.resultCode(500,"登录失败"));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

(3)登出成功处理类(LogoutSuccessHandler)

@Component
public class UserLogoutSuccessHandler implements LogoutSuccessHandler {
    /**
     * 用户登出返回结果,前端清除掉Token
     */
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication){
        Map<String,Object> resultData = new HashMap<>();
        resultData.put("code","200");
        resultData.put("msg", "登出成功");
        SecurityContextHolder.clearContext();
        ResultUtil.responseJson(response,ResultUtil.resultSuccess(resultData));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

(4)暂无权限处理类(AccessDeniedHandler)

@Component
public class UserAuthAccessDeniedHandler implements AccessDeniedHandler {
    /**
     * 暂无权限返回结果
     */
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception){
        ResultUtil.responseJson(response,ResultUtil.resultCode(403,"未授权"));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

(5)用户未登录处理类(AuthenticationEntryPoint)

@Component
public class UserAuthenticationEntryPointHandler implements AuthenticationEntryPoint {
    /**
     * 用户未登录返回结果
     */
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception){
        ResultUtil.responseJson(response,ResultUtil.resultCode(401,"未登录"));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

5.自定登录验证(AuthenticationProvider)


@Component
public class UserAuthenticationProvider implements AuthenticationProvider {
    @Autowired
    private UsersService usersService;
    @Autowired
    private UsermetaService usermetaService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // 获取表单输入中返回的用户名
        String userName = (String) authentication.getPrincipal();
        // 获取表单中输入的密码
        String password = (String) authentication.getCredentials();
        // 查询用户是否存在
        SelfUserEntity userInfo = usersService.getUserInfo(userName);

        if (userInfo.getUsername() == null || userInfo.getUsername() == "") {
            throw new UsernameNotFoundException("用户名不存在");
        }
        // 判断密码是否正确,这里我们的密码使用BCryptPasswordEncoder进行加密的
        if (!new BCryptPasswordEncoder().matches(password, userInfo.getPassword())) {
            throw new BadCredentialsException("密码不正确");
        }
        // 还可以加一些其他信息的判断,比如用户账号已停用等判断
        if (userInfo.getStatus().equals("1")) {
            throw new LockedException("该用户已被冻结");
        }
        // 角色集合
        Set<GrantedAuthority> authorities = new HashSet<>();

        EntityWrapper<Usermeta> roleWrapper = new EntityWrapper<>();
        roleWrapper.eq("user_id",userInfo.getUserId());
        roleWrapper.eq("meta_key","wp_user_level");
        // 查询用户角色
        List<Usermeta> sysRoleEntityList = usermetaService.selectList(roleWrapper);
        for (Usermeta sysRoleEntity: sysRoleEntityList){
            authorities.add(new SimpleGrantedAuthority("ROLE_" + sysRoleEntity.getMetaValue()));
        }
        userInfo.setAuthorities(authorities);
        // 进行登录
        return new UsernamePasswordAuthenticationToken(userInfo, password, authorities);
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return true;
    }
}
  • 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

6.自定义权限注解验证(PermissionEvaluator)

@Component
public class UserPermissionEvaluator implements PermissionEvaluator {
    @Autowired
    private UsermetaService usermetaService;

    /**
     * hasPermission鉴权方法
     * 这里仅仅判断PreAuthorize注解中的权限表达式
     * 实际中可以根据业务需求设计数据库通过targetUrl和permission做更复杂鉴权
     * 当然targetUrl不一定是URL可以是数据Id还可以是管理员标识等,这里根据需求自行设计
     * @Param  authentication  用户身份(在使用hasPermission表达式时Authentication参数默认会自动带上)
     * @Param  targetUrl  请求路径
     * @Param  permission 请求路径权限
     * @Return boolean 是否通过
     */
    @Override
    public boolean hasPermission(Authentication authentication, Object targetUrl, Object permission) {
        // 获取用户信息
        Usermeta selfUserEntity =(Usermeta) authentication.getPrincipal();
        // 查询用户权限(这里可以将权限放入缓存中提升效率)
        Set<String> permissions = new HashSet<>();
        EntityWrapper<Usermeta> roleWrapper = new EntityWrapper<>();
        roleWrapper.eq("user_id",selfUserEntity.getUserId());
        roleWrapper.eq("meta_key","wp_user_level");
        List<Usermeta> sysMenuEntityList = usermetaService.selectList(roleWrapper);
        for (Usermeta sysMenuEntity:sysMenuEntityList) {
            permissions.add(sysMenuEntity.getMetaValue());
        }
        // 权限对比
        if (permissions.contains(permission.toString())){
            return true;
        }
        return true;
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        return false;
    }
}
  • 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

7.用户实体类(UserDetails)

Spring Security用户的实体,必须要实现UserDetails接口。

public class SelfUserEntity implements Serializable, UserDetails {
    private static final long serialVersionUID = 1L;
    
    /**
     * 用户ID
     */
    private Long userId;
    
    /**
     * 用户名
     */
    private String username;
    
    /**
     * 密码
     */
    private String password;
    
    /**
     * 状态
     */
    private String status;
    
    /**
     * 显示名称
     */
    private String displayName;
    
    /**
     * 用户参数
     */
    private Map<String, String> userParamMap;
    
    /**
     * 用户角色
     */
    private Collection<GrantedAuthority> authorities;
    
    /**
     * 账户是否过期
     */
    private boolean isAccountNonExpired = false;
    
    /**
     * 账户是否被锁定
     */
    private boolean isAccountNonLocked = false;
    
    /**
     * 证书是否过期
     */
    private boolean isCredentialsNonExpired = false;
    
    /**
     * 账户是否有效
     */
    private boolean isEnabled = true;
	
	// 省略getter/setter
}
  • 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

8.JWT接口请求拦截器(BasicAuthenticationFilter)

JWT接口请求校验拦截器,请求接口时会进入这里验证Token是否合法和过期

public class JWTAuthenticationTokenFilter extends BasicAuthenticationFilter {
    public JWTAuthenticationTokenFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 获取请求头中JWT的Token
        String tokenHeader = request.getHeader(JWTConfig.tokenHeader);

        if (null != tokenHeader && tokenHeader.startsWith(JWTConfig.tokenPrefix)) {
            try {
                // 截取JWT前缀
                String token = tokenHeader.replace(JWTConfig.tokenPrefix, "");
                // 解析JWT
                Claims claims = Jwts.parser()
                        .setSigningKey(JWTConfig.secret)
                        .parseClaimsJws(token)
                        .getBody();
                // 获取用户名
                String username = claims.getSubject();
                String userId = claims.getId();

                if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(userId)) {
                    // 获取角色
                    List<GrantedAuthority> authorities = new ArrayList<>();
                    String authority = claims.get("authorities").toString();
                    if (!StringUtils.isEmpty(authority)) {
                        List<Map<String, String>> authorityMap = JSONObject.parseObject(authority, List.class);
                        for (Map<String, String> role : authorityMap) {
                            if (!StringUtils.isEmpty(role)) {
                                authorities.add(new SimpleGrantedAuthority(role.get("authority")));
                            }
                        }
                    }
                    //组装参数
                    SelfUserEntity selfUserEntity = new SelfUserEntity();
                    selfUserEntity.setUsername(claims.getSubject());
                    selfUserEntity.setUserId(Long.parseLong(claims.getId()));
                    selfUserEntity.setAuthorities(authorities);
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(selfUserEntity, userId, authorities);
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            } catch (ExpiredJwtException e) {
                System.out.println("Token过期");
            } catch (Exception e) {
                System.out.println("Token无效");
            }
        }
        filterChain.doFilter(request, response);
    }
}
  • 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

9.Spring Security用户的业务实现

@Component
public class SelfUserDetailsService implements UserDetailsService {

    @Autowired
    private UsersService usersService;

    /**
     * 查询用户信息
     *
     * @Param username  用户名
     * @Return UserDetails SpringSecurity用户信息
     */
    @Override
    public SelfUserEntity loadUserByUsername(String username) throws UsernameNotFoundException {
        EntityWrapper<Users> wrapper = new EntityWrapper<>();
        //邮箱正则表达式
        String expr = "^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})$";
        //是否为邮箱
        if (username.matches(expr)) {
            wrapper.eq("user_email", username);
        } else {
            wrapper.eq("user_login", username);
        }

        // 查询用户信息
        Users sysUserEntity = usersService.selectOne(wrapper);
        if (sysUserEntity != null) {
            // 组装参数
            SelfUserEntity selfUserEntity = new SelfUserEntity();
            BeanUtils.copyProperties(sysUserEntity, selfUserEntity);
            return selfUserEntity;
        }
        return null;
    }
}
  • 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

10.控制层Controller

@Secured
当@EnableGlobalMethodSecurity(securedEnabled=true)的时候,@Secured可以使用。

@PostMapping("/helloUser")
@Secured({"ROLE_normal","ROLE_admin"})
public Map<String, Object> initDashboard() {
	Map<String, Object> result = new HashMap<>();
	result.put(ResponseDict.RESPONSE_TITLE_KEY, "仪表盘初始化");
	result.put(ResponseDict.RESPONSE_DATA_KEY, dashboardService.initDashboard());
	return ResultUtil.resultSuccess(result);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

说明:拥有normal或者admin角色的用户都可以方法helloUser()方法。另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“

@PreAuthorize
Spring的 @PreAuthorize/@PostAuthorize 注解更适合方法级的安全,也支持Spring 表达式语言,提供了基于表达式的访问控制。

当@EnableGlobalMethodSecurity(prePostEnabled=true)的时候,@PreAuthorize可以使用:

@PostMapping("/initDashboard")
@PreAuthorize("hasRole('100')")
public Map<String, Object> initDashboard() {
    Map<String, Object> result = new HashMap<>();
    result.put(ResponseDict.RESPONSE_TITLE_KEY, "仪表盘初始化");
    result.put(ResponseDict.RESPONSE_DATA_KEY, dashboardService.initDashboard());
    return ResultUtil.resultSuccess(result);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

@PostAuthorize
@PostAuthorize 注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值的权限,Spring EL 提供 返回对象能够在表达式语言中获取返回的对象returnObject。

当@EnableGlobalMethodSecurity(prePostEnabled=true)的时候,@PostAuthorize可以使用:

@GetMapping("/getUserInfo")
@PostAuthorize(" returnObject!=null &&  returnObject.username == authentication.name")
public User getUserInfo() {
        Object pricipal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        User user;
        if("anonymousUser".equals(pricipal)) {
            user = null;
        } else {
            user = (User) pricipal;
        }
        return user;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

11.登录测试

(1)拿到正确token,正常登录
(2)请求中如果不携带token的话,请求其它接口就会显示没有登录的提示
(3)登录账户无权限


GrantedAuthority接口

在UserDeitails接口里面有一个getAuthorities()方法。这个方法将返回此用户的所拥有的权限。这个集合将用于用户的访问控制,也就是Authorization
①权限:就是一个字符串。一般不会重复
②权限检查:就是查看用户权限列表中是否含有匹配的字符串

public interface GrantedAuthority extends Serializable {
   // AccessDecisionManager访问控制决策,返回一个字符串
   String getAuthority();
}
  • 1
  • 2
  • 3
  • 4

“角色”如何表示?与Shiro有何不同?

在Spring Security中,角色和权限共用GrantedAuthority接口,唯一的不同角色就是多了个前缀“ROLE_”,而且它没有Shiro的那种从属关系,即一个角色包含哪些权限等等。在Spring Security看来角色和权限时一样的,它认证的时候,把所有权限(角色、权限)都取出来,而不是分开验证。所以,在Security提供的UserDetailsService默认实现JdbcDaoImpl中,角色和权限都存储在auhtorities表中。而不是像Shiro那样,角色有个roles表,权限有个permissions表。以及相关的管理表等

实现类:JaasGrantedAuthority、LdapAuthority、SimpleGrantedAuthority、SwitchUserGrantedAuthority
JaasGrantedAuthority GrantedAuthority除了分配的角色之外,还拥有主体,使用AuthorityGranter作为授予此权限的理由
SwitchUserGrantedAuthority 存储原始用户的身份验证对象,以便在以后从用户交换机“退出”时使用。

GrantedAuthority接口的默认实现SimpleGrantedAuthority

注意:在构建SimpleGrantedAuthority对象的时候,它没有添加任何前缀。所以表示“角色”的权限需要带有“ROLE_”前缀。Spring Security不管是角色、还是权限。它只比对字符串。所以角色需要加前缀。
角色信息存储的时候可以没有“ROLE_”前缀,包装成GrantedAuthority对象的时候必须要有

/**
* GrantedAuthority的基本具体实现。存储授予的权限的String(Authentication)
*/
public final class SimpleGrantedAuthority implements GrantedAuthority {
   private final String role;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

权限检查/访问控制方式

权限检查有两种方式:
①在配置类中,指定粗粒度的访问控制
②使用注解细粒度的控制访问

(1)粗粒度访问控制

所有URL以"/admin"开头的用户必须拥有角色"ADMIN"才能访问。实际上操作的时候hasRole表达式,会判断参数是否包含"ROLE_"前缀,如果没有则加上去,然后再去校验。有这个前缀则直接校验。

protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers("/admin/**").access("hasRole('ADMIN')")
            .antMatchers("/user/**").access("hasRole('USER')")
            .anyRequest().authenticated();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

(2)细粒度的访问控制

注:需要使用注解@EnableGlobalMethodSecurity(prePostEnabled=true)开启

@PreAuthoritze("hasAuthority('readArtical')")
public List<Artical> getAll() {
    //...
}
  • 1
  • 2
  • 3
  • 4

@PreAuthoritze注解,会从SecurityContext中取出Authencation对象,然后再取出Collection、 authorites集合。然后比对当前用户是否有权限"readArtical"。实际上就是比对集合中是否有那个GrantedAuthority的getAuthority()方法返回的字符串与"radArtical"匹配。


开启权限注解

在任何@Configuration实例上使用@EnableGlobalMethodSecurity注解就能达到此目的。同时这个注解提供prePostEnabled、securedEnabled和jsr250Enabled三种不同的机制来实现同一种功能

(1)prePostEnabled

prePostEnabled = true 会解锁@PreAuthorize和@PostAuthorize两个注解。从名字就可以看出@PreAuthorize注解会在方法执行前进行验证,而@PostAuthorize注解会在方法执行后进行验证。

public interface UserService {
    List<User> findAllUsers();

    @PostAuthorize ("returnObject.type == authentication.name")
    User findById(int id);
    
	//  @PreAuthorize("hasRole('ADMIN')") 必须拥有ROLE_ADMIN角色。
    @PreAuthorize("hasRole('ROLE_ADMIN ')")
    void updateUser(User user);
    
    @PreAuthorize("hasRole('ADMIN') AND hasRole('DBA')")
    void deleteUser(int id);
    
    // @PreAuthorize("principal.username.startsWith('Felordcn')") 用户名开头为 Felordcn 的用户才能访问。
    // @PreAuthorize("#id.equals(principal.username)") 入参 id 必须同当前的用户名相同。
    // @PreAuthorize("#id < 10") 限制只能查询 id 小于 10 的用户

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

常见内置表达式
https://docs.spring.io/spring-security/site/docs/4.0.1.RELEASE/reference/htmlsingle/#el-common-built-in
在这里插入图片描述

@PostAuthorize:
该注解使用不多,在方法执行后再进行权限验证。 适合验证带有返回值的权限。Spring EL 提供 返回对象能够在表达式语言中获取返回的对象returnObject。区别在于先执行方法。而后进行表达式判断。如果方法没有返回值实际上等于开放权限控制;如果有返回值实际的结果是用户操作成功但是得不到响应。允许方法调用,但是如果表达式计算结果为false,将抛出一个安全性异常。

@PreFilter:
对集合类型的参数执行过滤,移除结果为false的元素。基于方法入参相关的表达式,对入参进行过滤。分页慎用!该过程发生在接口接收参数之前。入参必须为 java.util.Collection且支持remove(Object)的参数。如果有多个集合需要通过 filterTarget=<参数名> 来指定过滤的集合。内置保留名称 filterObject 作为集合元素的操作名来进行评估过滤。

// 指定过滤的参数,过滤偶数
@PreFilter(filterTarget="ids", value="filterObject%2==0")
public void delete(List<Integer> ids, List<String> username)
  • 1
  • 2
  • 3

@PostFilter:
和@PreFilter不同的是, 基于返回值相关的表达式,对返回值进行过滤。分页慎用!该过程发生接口进行数据返回之前


(2)Secured

@Secured注解是用来定义业务方法的安全配置。在需要安全[角色/权限等]的方法上指定 @Secured,并且只有那些角色/权限的用户才可以调用该方法。

@Secured缺点(限制)就是不支持Spring EL表达式。不够灵活。并且指定的角色必须以ROLE_开头,不可省略。该注解功能要简单的多,默认情况下只能基于角色(默认需要带前缀 ROLE_)集合来进行访问控制决策。该注解的机制是只要其声明的角色集合(value)中包含当前用户持有的任一角色就可以访问。也就是 用户的角色集合和 @Secured 注解的角色集合要存在非空的交集。 不支持使用 SpEL 表达式进行决策。

    @Secured({"ROLE_user"})
    void updateUser(User user);

    @Secured({"ROLE_admin", "ROLE_user1"})
    void updateUser();
  • 1
  • 2
  • 3
  • 4
  • 5

(3)jsr250E

启用JSR-250安全控制注解,这属于JavaEE的安全规范(现为jakarta项目)。一共有五个安全注解。如果在@EnableGlobalMethodSecurity设置jsr250Enabled为true ,就开启了JavaEE 安全注解中的以下三个:

1.@DenyAll: 拒绝所有访问
2.@RolesAllowed({“USER”, “ADMIN”}): 该方法只要具有"USER", "ADMIN"任意一种权限就可以访问。这里可以省略前缀ROLE_,实际的权限可能是ROLE_ADMIN
3.@PermitAll: 允许所有访问

/*
 * 配置没有权限自定义处理类
 *
 * 注意:hasRole(),如果出现异常,会调用设置的accessDeniedHandler方法。
 *  .antMatchers("/index").hasRole("ADMIN")
 *  .anyRequest().authenticated();
 */
httpSecurity.exceptionHandling()
        .accessDeniedHandler(userAuthAccessDeniedHandler);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/546338
推荐阅读
相关标签
  

闽ICP备14008679号