SpringSecurity+JWT实现权限校验

1通过@ConfigurationProperties 读取配置文件的配置,允许用户自定义规则

@ConfigurationProperties(prefix = "security.config")
@Data
public class SecurityConfigProperties {
    
    

    /**
     * token请求头名称
     * */
    private String tokenHeader;

    /**
     * token加解密使用的密钥
     * */
    private String tokenSecret;

    /**
     * token过期时间(秒)
     * */
    private Long tokenExpiration;


    /**
     * 访问路径白名单,无需登陆即可访问
     * */
    private List<String> ignoreUrls;

}

2 创建JWT工具类,用于生成和解析token

@Component
public class JwtTokenUtil {
    
    

    private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class);

    //存入token内的用户名的kye
    private static final String CLAIM_KEY_USERNAME = "sub";
    //存入token的创建时间
    private static final String CLAIM_KEY_CREATED = "created";

    @Autowired
    private SecurityConfigProperties securityConfigProperties;



    /**
     * 根据负责生成JWT的token
     */
    private String generateToken(Map<String, Object> claims) {
    
    
        //token加密密钥
        String tokenSecret = securityConfigProperties.getTokenSecret();

        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(generateExpirationDate())
                .signWith(SignatureAlgorithm.HS512, tokenSecret )
                .compact();
    }

    /**
     * 从token中获取JWT中的负载
     */
    private Claims getClaimsFromToken(String token) {
    
    
        Claims claims = null;
        //token加密密钥
        String tokenSecret = securityConfigProperties.getTokenSecret();
        try {
    
    
            claims = Jwts.parser()
                    .setSigningKey(tokenSecret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
    
    
            LOGGER.info("JWT格式验证失败:{}",token);
        }
        return claims;
    }

    /**
     * 生成token的过期时间
     */
    private Date generateExpirationDate() {
    
    
        long expiration = securityConfigProperties.getTokenExpiration();
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }

    /**
     * 从token中获取登录用户名
     */
    public String getUserNameFromToken(String token) {
    
    
        String username;
        try {
    
    
            Claims claims = getClaimsFromToken(token);
            username =  claims.getSubject();
        } catch (Exception e) {
    
    
            username = null;
        }
        return username;
    }

    /**
     * 验证token是否还有效
     *
     * @param token       客户端传入的token
     * @param userDetails 从数据库中查询出来的用户信息
     */
    public boolean validateToken(String token, UserDetails userDetails) {
    
    
        String username = getUserNameFromToken(token);
        return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
    }

    /**
     * 判断token是否已经失效
     */
    private boolean isTokenExpired(String token) {
    
    
        Date expiredDate = getExpiredDateFromToken(token);
        return expiredDate.before(new Date());
    }

    /**
     * 从token中获取过期时间
     */
    private Date getExpiredDateFromToken(String token) {
    
    
        Claims claims = getClaimsFromToken(token);
        return claims.getExpiration();
    }

    /**
     * 根据用户信息生成token
     */
    public String generateToken(UserDetails userDetails) {
    
    
        Map<String, Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }

    /**
     * 判断token是否可以被刷新
     */
    public boolean canRefresh(String token) {
    
    
        return !isTokenExpired(token);
    }

    /**
     * 刷新token
     */
    public String refreshToken(String token) {
    
    
        Claims claims = getCla3 imsFromToken(token);
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }
}

3 继承WebSecurityConfigurerAdapter类,配置security权限校验逻辑

@Configuration
@Slf4j
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
@ConditionalOnBean(UserDetailsService.class)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    

    /**
     * 必须实现此接口,用于通过用户名获取当前登陆账号状态
     * */
    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    /**
     * 当访问接口没有权限时,如要自定义的返回结果,应实现 AccessDeniedHandler  接口
     * */
    @Autowired(required = false)
    private AccessDeniedHandler accessDeniedHandler;

    /**
     * 当未登录或者token失效访问接口时,如要自定义的返回结果,应实现 AuthenticationEntryPoint 接口
     * */
    @Autowired(required = false)
    private AuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    private SecurityConfigProperties securityConfigProperties;

    @Autowired
    private PasswordEncoder passwordEncoder;


    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
    
    

        //允许不用登陆即可访问的url
        List<String> ignoreUrls = securityConfigProperties.getIgnoreUrls();


        log.info("允许匿名访问的接口==>"+ignoreUrls);

        httpSecurity.csrf()// 由于使用的是JWT,我们这里不需要csrf
                .disable()
                .sessionManagement()// 基于token,所以不需要session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 允许对于网站静态资源的无授权访问
                .antMatchers(HttpMethod.GET,
                        "/",
                        "/*.html",
                        "/favicon.ico",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js",
                        "/swagger-resources/**",
                        "/v2/api-docs/**"
                )
                .permitAll()
                //将配置的路径设置为无权即可访问
                .antMatchers(ignoreUrls.toArray(new String[0]))
                .permitAll()
                //跨域请求会先进行一次options请求
                .antMatchers(HttpMethod.OPTIONS)
                .permitAll()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest()
                .authenticated();
        // 禁用缓存
        httpSecurity.headers().cacheControl();

        // 添加JWT filter
        httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);


        if(accessDeniedHandler!=null && authenticationEntryPoint !=null){
    
    
            //添加自定义未授权和未登录结果返回
            httpSecurity.exceptionHandling()
                    .accessDeniedHandler(accessDeniedHandler)
                    .authenticationEntryPoint(authenticationEntryPoint);
        }


    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder);
    }


    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
    
    
        return super.authenticationManagerBean();
    }
}

4 实现 userDetailService和

@Component
@Slf4j
public class MyUserDetailsService implements UserDetailsService {
    
    

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    
        //这里为了易懂写死了,应该按需求查询数据库进行校验
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

        MyUserDetails userDetails = new MyUserDetails();
        if("张三".equals(username)){
    
    
            userDetails = new MyUserDetails();

            userDetails.setPassword(passwordEncoder.encode("zs"));
            userDetails.setUsername("张三");

            ArrayList<GrantedAuthority> arrayList = new ArrayList();
            arrayList.add(new SimpleGrantedAuthority("zs"));
            userDetails.setAuthorities(arrayList);
        }else{
    
    
            userDetails = new MyUserDetails();

            userDetails.setPassword(passwordEncoder.encode("qt"));
            userDetails.setUsername("其他");

            ArrayList<GrantedAuthority> arrayList = new ArrayList();
            arrayList.add(new SimpleGrantedAuthority("qt"));
            userDetails.setAuthorities(arrayList);
        }
        return userDetails;
    }

    public String login(String username, String password) {
    
    
        String token = null;
        //密码需要客户端加密后传递
        try {
    
    
            UserDetails userDetails = loadUserByUsername(username);

            if(!passwordEncoder.matches(password,userDetails.getPassword())){
    
    
               throw new MyException(ResultStatus.ERROR);
            }

            if(!userDetails.isEnabled()){
    
    
                throw new MyException(ResultStatus.ERROR);
            }

            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());

            SecurityContextHolder.getContext().setAuthentication(authentication);
            token = jwtTokenUtil.generateToken(userDetails);
        } catch (AuthenticationException e) {
    
    
            log.warn("登录异常:{}", e.getMessage());
        }
        return token;
    }


}
@NoArgsConstructor
@AllArgsConstructor
@Data
public class MyUserDetails implements UserDetails {
    
    


    private List<GrantedAuthority> authorities;

    private String username;
    private String password;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
    
    

        return authorities;
    }

    @Override
    public String getPassword() {
    
    

        return password;
    }

    @Override
    public String getUsername() {
    
    

        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
    
    

        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
    
    

        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
    
    

        return true;
    }

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

5 创建过滤器获取请求的token后解析,如通过,将当前登陆信息存入SecurityContext

@ConditionalOnBean(UserDetailsService.class)
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    
    

    /**
     * 必须实现此接口 用户登陆时通过用户名获取用户信息
     * */

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private SecurityConfigProperties securityConfigProperties;

    @Autowired
    public PathMatcher pathMatcher;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws ServletException, IOException {
    
    

        String tokenName = securityConfigProperties.getTokenHeader();
        //获取请求头
        String authToken = request.getHeader(tokenName);


        if (authToken != null) {
    
    

            //获取当前登陆用户名称
            String username = jwtTokenUtil.getUserNameFromToken(authToken);


            //当前用户名不为空 并且是未登录状态
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
    
    

                //通过用户名查询用户
                UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);

                //验证Token
                if (jwtTokenUtil.validateToken(authToken, userDetails)) {
    
    
                    //创建凭证
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());

                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    //放入凭证
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }

        chain.doFilter(request, response);
    }
}

猜你喜欢

转载自blog.csdn.net/dndndnnffj/article/details/111768923