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);
    }
}
Spring Security JWT实现是指使用JWT(JSON Web Token)作为身份验证和授权机制的Spring Security解决方案。Spring SecurityJWT提供了自动化配置,使得使用JWT进行身份验证和授权变得更加简单和高效。通过配置JwtAuthenticationTokenFilter,可以实现JWT的验证和解析。同时,可以通过RestfulAccessDeniedHandler和RestAuthenticationEntryPoint来处理登录校验权限校验的逻辑。使用JWT实现Spring Security解决方案可以提供更加强大和灵活的身份验证和授权功能。 [2 [3<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [厉害,我带的实习生仅用四步就整合好SpringSecurity+JWT实现登录认证](https://blog.csdn.net/qing_gee/article/details/124016059)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [单点登录SSO解决方案之SpringSecurity+JWT实现.docx](https://download.csdn.net/download/njbaige/34581331)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值