基于session认证上添加jwt认证

前言

此文章是基于已有SpringSecurity的默认cookie认证的基础上再新增jwt认证,这种适用于既可以运转传统的一体网站,又可以给只有前端的网站提供后台RestAPI服务。当然,此文章也可做为Spring Security兼容jwt的案例来看。

添加依赖

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

对Spring Security的SecurityConfig的相关配置

我们已经有一个SecurityConfig配置文件用来配置默认cookie认证的相关信息,再新增一个JWTSecurityConfig配置文件,用@Order(1)标注(有标注的比没有此标注的优先匹配):

  1. 禁用Session(configure(HttpSecurity http)方法中);

       http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    
  2. 关闭csrf和frameOptions,如果不关闭会影响前端请求接口;

      http.csrf().disable();
      http.headers().frameOptions().disable();
    
  3. 开启跨域以便前端调用接口;

      http.cors();
    
  4. 配置jwt认证作用下的所有接口,假如用“/jwt/”开头的接口都为jwt所有,"/jwt/login"是jwt下login的url;

     http.antMatcher("/jwt/**")
         .authorizeRequests()
         // login url
         .antMatchers("/jwt/login").permitAll()
         // 其它所有接口需要认证才能访问
         .anyRequest().authenticated()
         // 指定认证错误处理器
         .and().exceptionHandling().authenticationEntryPoint(new MyEntryPoint());
    
  5. 在userpasswordFilter之前添加loginTokenFilter

      http.addFilterBefore(loginTokenFilter, UsernamePasswordAuthenticationFilter.class);
    

login返回jwt过程

//LoginController 
@RestController
@RequestMapping(value = "/jwt")
public class LoginController {
    @Autowired
    private  GetTokenUserService getTokenUserService;

    private static LogConfig log = LogConfig.getLogger(LoginController .class);
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public UserTokenBean systemLoginAction(@RequestBody LoginParam user) {
        return getTokenUserService.login(user);
    }

}
//GetTokenUserService 
@Service
public class GetTokenUserService {

    private static LogConfig log = LogConfig.getLogger(GetTokenUserService.class);
    
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    
    public UserTokenBean login(LoginParam param) throws UsernameNotFoundException {
        //省略查询条件
        ...
        UserDetail userDetail = UserMapper.selectByExample(example);
        UserTokenBean userTokenBean = new UserTokenBean();
        userTokenBean.setUserDetail(userDetail); 
        // 生成JWT,将用户名数据存入其中
        userTokenBean.setToken(jwtTokenUtil.generate(param.getLoginId()));
        return userTokenBean;
    }
}

//JwtTokenUtil 

@Component
public class JwtTokenUtil {
    /**
     * 这个秘钥是防止JWT被篡改的关键,随便写什么都好,但决不能泄露
     */
    private final static String secretKey = "whatever11gdb";
    /**
     * 过期时间目前设置成2天,这个配置随业务需求而定
     */
    private final static Duration expiration = Duration.ofHours(2);

    /**
     * 生成JWT
     *
     * @param userName 用户名
     * @return JWT
     */
    public static String generate(String userName) {
        // 过期时间
        Date expiryDate = new Date(System.currentTimeMillis() + expiration.toMillis());

        return Jwts.builder()
                .setSubject(userName) // 将userName放进JWT
                .setIssuedAt(new Date()) // 设置JWT签发时间
                .setExpiration(expiryDate)  // 设置过期时间
                .signWith(SignatureAlgorithm.HS512, secretKey) // 设置加密算法和秘钥
                .compact();
    }

    /**
     * 解析JWT
     *
     * @param token JWT字符串
     * @return 解析成功返回Claims对象,解析失败返回null
     */
    public static Claims parse(String token) {
        // 如果是空字符串直接返回null
        if (StringUtils.isEmpty(token)) {
            return null;
        }

        // 这个Claims对象包含了许多属性,比如签发时间、过期时间以及存放的数据等
        Claims claims = null;
        // 解析失败了会抛出异常,所以我们要捕捉一下。token过期、token非法都会导致解析失败
        try {
            claims = Jwts.parser()
                    .setSigningKey(secretKey) // 设置秘钥
                    .parseClaimsJws(token)
                    .getBody();
        } catch (JwtException e) {
            // 这里应该用日志输出,为了演示方便就直接打印了
            System.err.println("解析失败!");
        }
        return claims;
    }
}

loginTokenFilter

@Component
public class LoginTokenFilter extends OncePerRequestFilter {
    @Autowired
    private JwtTokenUtil jwtManager;
    @Autowired
    private GetUserService userService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        // 从请求头中获取token字符串并解析
        Claims claims = jwtManager.parse(request.getHeader("Authorization"));
        if (claims != null) {
            // 从`JWT`中提取出之前存储好的用户名
            String username = claims.getSubject();
            // 查询出用户对象
            UserDetails user = userService.loadUserByUsername(username);
            // 手动组装一个认证对象
            Authentication authentication = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
            // 将认证对象放到上下文中
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        chain.doFilter(request, response);
    }
}

自定义错误处理MyEntryPoint

@Component
public class MyEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        // 直接提示前端认证错误
        out.write("认证错误");
        out.flush();
        out.close();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值