一、核心流程

  1. 引入依赖
  2. 创建JwtToken工具类
  3. 在SecurityConfig中配置使用successHandler自定义含JwtToken的响应数据
  4. 在SecurityConfig中使用addFilterBefore自定义JwtFilter过滤器

在第3步中,需要做一个登录验证成功时的拦截,拦截后生成JwtToken然后写入到响应内容里面,这样前端就能接收到通过/login请求获取到的JwtToken数据。于此同时,也需要将拦截时获取到的authentication写入到SecurityContextHolder中,为下次前端通过JwtToken验证做准备。

在第4步中,通过增加JwtFilter过滤器来验证身份,允许前端通过携带正确且未过期的JwtToken进行身份验证,验证时需要同样需要通过LoadUserByUsername方法获取UserDetails对象(Principal对象),然后创建一个UsernamePasswordAuthenticationToken对象给SecurityContextHolder验证,如果和步骤2中写入的authentication一致,即验证成功,正确获取到用户权限。

二、代码实现

2.1 引入依赖

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>4.0.0</version>
</dependency>

2.2 创建JwtToken工具类

JwtToken工具类的核心功能包括创建token和验证token

package com.example.demo.utils.jwt;

/**
 * @Author : HuangJiajian
 * @create 2022/9/28 10:29
 */
public class JwtUtils {
    private static final String secret = "test123456";

    /**
     * @Author HJJ
     * @Date 2022-12-28 16:59
     * @Params  userId,userName
     * @Return token
     * @Description 创建Token,默认时间为7天
     */
    public static String createToken(String userName, String authentications){
        long expire = 7 * 24 * 3600 * 1000;
        return JWT.create().withAudience(userName)
                .withIssuedAt(new Date())
                .withExpiresAt(new Date(System.currentTimeMillis()+ expire))
                .withClaim("authentications", authentications)
                .sign(Algorithm.HMAC256(userName+secret));
    }

    /**
     * @Author HJJ
     * @Date 2022-12-28 16:59
     * @Params token, userName
     * @Return boolean
     * @Description 验证Token,过期或不正确时会返回false
     */
    public static boolean verifyToken(String token, String userName){
        DecodedJWT jwt = null;
        try {
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(userName+secret)).build();
            jwt = verifier.verify(token);
            return true;
        } catch (Exception e) {
            System.out.println(e);
        }
        return false;
    }
}

3.3 配置successHandler

在SecurityConfig文件中配置登录验证成功时的拦截器,详细配置可见另一篇文章

package com.example.demo.config.security;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        // 登录
        httpSecurity.formLogin()
                .loginProcessingUrl("/login")
                .loginPage("/login.html")
                .successHandler(new AuthenticationSuccessConfig())
                .failureHandler(new AuthenticationFailConfig());
    }
}

自定义AuthenticationSuccessConfig登录成功拦截,重点需要关注将authentication写入SecurityContextHolder的步骤将JwtToken写入response的步骤

package com.example.demo.config.security;

/**
 * @Author : HuangJiajian
 * @create 2022/10/20 16:24
 */
public class AuthenticationSuccessConfig implements AuthenticationSuccessHandler {
    /**
     * @Author HJJ
     * @Date 2022-12-28 17:20
     * @Params
     * @Return
     * @Description 整合JWT,在身份验证成功时,生成JwtToken并将authentication写入SpringSecurity中
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        UsernamePasswordAuthenticationToken user = (UsernamePasswordAuthenticationToken) authentication;
        String token = JwtUtils.createToken(user.getName(), user.getAuthorities().toString());
        SecurityContextHolder.getContext().setAuthentication(authentication);
        response.setContentType("text/json;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write(JSON.toJSONString(new AjaxResult(AjaxResult.CODE.SUCCESS, "success", token)));
        writer.flush();
        writer.close();
    }
}

3.4 配置addFilterBefore

在SecurityConfig文件中配置Filter,详细配置可见另一篇文章

package com.example.demo.config.security;

@Configuration
public class SecurityConfig {

    @Autowired
    private JwtFilter jwtFilter;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

自定义JwtFilter拦截器,

package com.example.demo.config.security;

/**
 * @Author : HuangJiajian
 * @create 2022/12/29 9:14
 */
@Component
public class JwtFilter extends OncePerRequestFilter {
    @Autowired
    private UserDetailServiceImpl userDetailsService;
    /**
     * @Author HJJ
     * @Date 2022-12-29 9:18
     * @Params
     * @Return
     * @Description 获取请求中携带的JwtToken信息,并验证其真实性
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = "";
        String username = "";
        for (Cookie cookie : request.getCookies()) {
            if (cookie.getName().equals("username")) {
                username = cookie.getValue();
            }
            if (cookie.getName().equals("token")) {
                token = cookie.getValue();
            }
        }
        if (token.isEmpty() || username.isEmpty()){
            filterChain.doFilter(request, response);
            return;
        }
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        if (JwtUtils.verifyToken(token, username)) {
            // 验证成功后,将authentication与在第一次登录时写入的权限做对比,如果成功即可跳过原本的验证了
            UsernamePasswordAuthenticationToken authentication = 
            		new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } else {
            System.out.println(username + "JwtToken验证失败");
        }
        filterChain.doFilter(request, response);
    }
}

以上就是SpringSecurity整合JwtToken的全过程。

参考文献

  1. Spring Security整合JWT实现前后端分离认证和权限管理(超详细)

更多推荐