SpringSecurity+JWT实现

SpringSecurity+JWT

JWT认证和Session认证

参考资料:什么是 JWT – JSON WEB TOKEN - 简书 (jianshu.com)

JWT:JSON WEB TOKEN

JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

传统Session认证存在的问题

因为http协议本身是无状态协议,即时用户第一次请求时提供了用户名和密码进行认证,第二次请求时仍需要提供用户名和密码,因为对后端来说并不能知道是哪个用户发出的请求。

传统的Session认证就是用户登录成功后,将用户信息存放在后台session中,前端下次请求时需要在cookie中携带对应的sessionid才能表明其已经登录过。

问题

  • 因为用户信息需要存放在后端的session中(后端内存),当用户信息增多时服务端内存开销变大

  • 用户认证后信息保存在当前服务器中,但对于分布式应用来说,其他服务器服务器中是没有该服务器中的session信息的

  • CSRF跨域问题:session认证的结果被存放在cookie中,攻击者是可以在你访问网站时获取到你的cookie并且伪造请求去请求信息

    (49条消息) 了解cookie以及cookie跨站点伪造攻击(CSRF)_周刚的专栏-CSDN博客

JWT认证方式

token的认证方式下,服务端不需要为用户保存认证信息

大致流程

  • 用户账号密码请求服务器
  • 服务器认证通过
  • 生成一个token(jwt),该jwt是加密的,需要服务端有对于的私钥才能解密。
  • 前端存储用户token,并在后续请求头中携带token
  • 服务器验证token的值并解密取出token中的信息再次生成security中需要的authentication对象。

具体实现

因为Security的认证流程就是一串和UsernamePasswordFilter类似的过滤器在过滤器链上拦截请求然后认证,认证通过就返回认证信息即可,我们可以模拟UsernamePasswordFilter写一个自己的UsernamePasswordFilter

1. JwtFilter

@Slf4j
@RequiredArgsConstructor
@Component
public class JwtFilter extends OncePerRequestFilter {

    private final AppProperties appProperties;
    private final JwtUtil jwtUtil;
    private final ObjectMapper objectMapper;
    /**
    	主要拦截方法,
    	这个方法负责对所有进入的请求判断请求头中是否有token
    */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        var claimsOpt = Optional.ofNullable((Claims) null);
        
        if (checkJwtToken(request)) {
        	// 如果存在token则对token进行校验
            claimsOpt = validateToken(request);
            claimsOpt.filter(claims -> Objects.nonNull(claims.get("authorities")))
                    .ifPresentOrElse(
                    		// 将信息设置到SecurityContext中
                            this::setSpringAuthentication,
                            SecurityContextHolder::clearContext
                    );
        }
        filterChain.doFilter(request, response);
    }

	// 判断是否存在token,并且token是否以Bearer 开头
    private boolean checkJwtToken(HttpServletRequest request) {
        var header = request.getHeader(appProperties.getJwt().getHeader());
        return Strings.isNotEmpty(header) && header.startsWith(appProperties.getJwt().getPrefix());
    }

    /**
     * 获取 Token 中的Claims中保存的信息,无则返回 empty()
     */
    private Optional<Claims> validateToken(HttpServletRequest request) {
        var token = request.getHeader(appProperties.getJwt().getHeader()).replace(appProperties.getJwt().getPrefix(), "");
        try {
            return Optional.of(Jwts.parserBuilder().setSigningKey(jwtUtil.getKey()).build().parseClaimsJws(token).getBody());
        } catch (ExpiredJwtException | SignatureException | MalformedJwtException | UnsupportedJwtException | IllegalArgumentException e) {
            return Optional.empty();
        }
    }

    private void setSpringAuthentication(Claims claims) {
    	// 这里我是直接从token中解析出的用户信息,安全的方式是只在token中存放基本的用户信息,再这里去重新查询用户信息
        var rawList = CollectionUtil.convertObjectToList(claims.get("authorities"));
        var authorities = rawList.stream()
                .map(String::valueOf)
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
        var authentication = new UsernamePasswordAuthenticationToken(claims.getSubject(), null, authorities);
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }
}


2. SecurityConfig配置类

public class BaseSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    protected JwtFilter jwtFilter;
    // ....
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    	// 这里省略的其他security的配置代码,只给出了如何将filter设置到UsernamePasswordAuthenticationFilter之前的配置
        http
        		// ....
                .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
    }

}

3. 生成JWT的工具类

pom.xml中导入依赖

<!--JWT-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>

创建JwtTokenUtils类

@Slf4j
public class JwtTokenUtils {

    public static final String TOKEN_HEADER = "Authorization";
    public static final String TOKEN_PREFIX = "Bearer ";
    //密钥自定义
    private static final String SECRET = "xxx";
    //签授人自定义
    private static final String ISS = "XXX";

    // 过期时间是3600秒,既是1个小时 单位秒
    private static final long EXPIRATION = 7200L;

    // 选择了记住我之后的过期时间为7天 单位秒
    private static final long EXPIRATION_REMEMBER = 604800L;

    // 创建token
    public static String createToken(String subject,HashMap<String,Object> claims,boolean isRememberMe) {
        long expiration = isRememberMe ? EXPIRATION_REMEMBER : EXPIRATION;
        return Jwts.builder()
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .setClaims(claims)
                .setIssuer(ISS)
                .setSubject(subject)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
                .compact();
    }

    // 从token中获取用户名
    public static String getUsername(String token){
        return getTokenBody(token).getSubject();
    }


    // 是否已过期
    public static boolean isExpiration(String token) {
        try {
            return getTokenBody(token).getExpiration().before(new Date());
        } catch (ExpiredJwtException e) {
            return true;
        }
    }
	
    private static Claims getTokenBody(String token){
        return Jwts.parser()
                .setSigningKey(SECRET)
                .parseClaimsJws(token)
                .getBody();
    }
    //解析token并获取我们存放的信息
    public static Object getClaims(String token,String claim){
        return getTokenBody(token).get(claim);
    }

    public static String createJwt(User user,Integer rememberMe) throws IOException {
        boolean isRemember = rememberMe == 1;
        Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
        String role = null;
        for (GrantedAuthority authority : authorities) {
            role = authority.getAuthority();
        }
        //在token中放入要保存的用户信息
        //这里存放的保存的信息就是用户二次请求时我们能获取到的信息
        HashMap<String,Object> claims = new HashMap<>();
        claims.put("USER_ROLE",role);
        claims.put("USER_NAME",user.getUsername());
        claims.put("USER_REAL_NAME",user.getRealName());
        //创建token并返回
        return JwtTokenUtils.createToken(user.getUsername(),claims , isRemember);
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shenyang1026

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值