通过JWT+SpringSecurity实现用户登录

一、Jwt

1.引入Jwt依赖

以下内容皆为针对该依赖提供。

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

2、生成token

        JWT由3部分组成:标头(Header)、有效载荷(Payload)和签名(Signature)。生成token时需要配置token的主体部分中的字段值(并非每个字段都需要配置),通过引入的依赖中的Jwts类提供的对应的set方法即可配置对应的字段值,如下:

字段名含义对应Jwts的方法
iss发行人setIssuer()
exp到期时间setExpiration()
sub主题setSubject()
aud用户setAudience()
nbf在此之前不可用setNotBefore()
iat发布时间setIssuedAt()
jtiJWT ID用于标识该JWTsetId()

        Jwts存储用户设置有效载荷部分的字段是通过底层JwtMap类中定义的Map存放这些字段的key-value键值对实现,下面是JwtMap的部分源码。

public class JwtMap implements Map<String, Object> {
    private final Map<String, Object> map;

        在Claims接口中定义了对应的get()、set() 抽象方法用于向map中存放对应的键值对,DefaultClaims类继承了JwtMap类并实现了Claims接口中的方法。

Claims接口的部分源码:

public interface Claims extends Map<String, Object>, ClaimsMutator<Claims> {
    String ISSUER = "iss";
    String SUBJECT = "sub";
    String AUDIENCE = "aud";
    String EXPIRATION = "exp";
    String NOT_BEFORE = "nbf";
    String ISSUED_AT = "iat";
    String ID = "jti";

    String getIssuer();

    Claims setIssuer(String var1);

DefaultClaims类的部分源码:

public class DefaultClaims extends JwtMap implements Claims {
    public DefaultClaims() {
    }

    public DefaultClaims(Map<String, Object> map) {
        super(map);
    }

    public String getIssuer() {
        return this.getString("iss");
    }

    public Claims setIssuer(String iss) {
        this.setValue("iss", iss);
        return this;
    }

        用户通过调用对应的get()、set()方法即可实现对jwt中字段的值(map)填充,填充后调用compact()方法生成token,该方法会根据用户配置生成jwt标头,并将用户填充的map转为base64加密后的字符串,还会根据用户配置的签证密钥生成一串签证字符串,最后将三者拼接成最终的jwt串返回,下面是生成jwt方法源码。

            Header header = this.ensureHeader();
            Key key = this.key;
            if (key == null && !Objects.isEmpty(this.keyBytes)) {
                key = new SecretKeySpec(this.keyBytes, this.algorithm.getJcaName());
            }

            Object jwsHeader;
            if (header instanceof JwsHeader) {
                jwsHeader = (JwsHeader)header;
            } else {
                jwsHeader = new DefaultJwsHeader(header);
            }

            if (key != null) {
                ((JwsHeader)jwsHeader).setAlgorithm(this.algorithm.getValue());
            } else {
                ((JwsHeader)jwsHeader).setAlgorithm(SignatureAlgorithm.NONE.getValue());
            }

            if (this.compressionCodec != null) {
                ((JwsHeader)jwsHeader).setCompressionAlgorithm(this.compressionCodec.getAlgorithmName());
            }

            //生成base64jwt标头
            String base64UrlEncodedHeader = this.base64UrlEncode(jwsHeader, "Unable to serialize header to json.");
            String base64UrlEncodedBody;
            if (this.compressionCodec != null) {
                byte[] bytes;
                try {
                    bytes = this.payload != null ? this.payload.getBytes(Strings.UTF_8) : this.toJson(this.claims);
                } catch (JsonProcessingException var9) {
                    throw new IllegalArgumentException("Unable to serialize claims object to json.");
                }

                base64UrlEncodedBody = TextCodec.BASE64URL.encode(this.compressionCodec.compress(bytes));
            } else {
                //this.claims中包含用户配置的jwt信息的map,将其转为base64
                base64UrlEncodedBody = this.payload != null ? TextCodec.BASE64URL.encode(this.payload) : this.base64UrlEncode(this.claims, "Unable to serialize claims object to json.");
            }

            String jwt = base64UrlEncodedHeader + '.' + base64UrlEncodedBody;
            if (key != null) {
                //key为用户配置的密钥,用于生成签证。
                JwtSigner signer = this.createSigner(this.algorithm, (Key)key);
                String base64UrlSignature = signer.sign(jwt);
                jwt = jwt + '.' + base64UrlSignature;
            } else {
                jwt = jwt + '.';
            }

            return jwt;

        代码中的具体使用:

    String token = Jwts.builder()
                .setId("jwt唯一标识")
                .setSubject("jwt主题")
                .setIssuedAt(new Date())
                .claim("该部分可放入自定义信息,也可不放")
                .setIssuer("zcg")
                .setExpiration(new Date(System.currentTimeMillis() + "配置过期时间,单位ms"))
                .signWith(SignatureAlgorithm.HS512, JWTConfig.secret)
                .compact();
    return token;

3、登录验证token

         解析部分即对token三部分的base64解码,主要是将有效载荷部分的数据解析后放入Claims类中共用户直接获取token中的信息(注:源码中会获取当前时间与token的过期时间进行对比,若token过期则会直接抛出ExpiredJwtException异常)。

    //解析Jwt
    Claims claims = Jwts.parser()
        .setSigningKey(token密钥)
        .parseClaimsJws(token字符串)
        .getBody();

        在具体使用中需要定义请求过滤器用于验证用户发请求时携带的token。可以通过继承BasicAuthenticationFilter并覆盖其doFilterInternal方法实现具体的验证逻辑,下面是过滤器部分代码。

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse                                     response, FilterChain chain) throws IOException, ServletException {
        //获取请求头中的JWT的Token
        String tokenHeader = request.getHeader(JWTConfig.tokenHeader);
        if (null != tokenHeader) {
            //解析JWT
            Claims claims = Jwts.parser()
                .setSigningKey(JWTConfig.secret)
                .parseClaimsJws(tokenHeader)
                .getBody();
            //获取权限
            String auth = claims.get("authorities").toString();
            //获取用户名与唯一标识
            String username = claims.getSubject();
            Integer id = Integer.parseInt(claims.getId());
            //获取过期时
            long expTime = claims.getExpiration().getTime();

二、SpringSecurity

1.引入SpringSecurity依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

2.定义登录成功处理器

        定义登录成功类,并继承AuthenticationSuccessHandler,实现其onAuthenticationSuccess方法,在该方法中生成token并返回,前端每次请求接口时携带该token,拦截器再进行验证token操作。

    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        //生成token,生成方法上文已贴代码
        String token = JWTTokenUtil.createAccessToken(userSecurity);
        //自定义方法通过response返回token
        ResultUtil.responseJson(response, resultData);

3.配置SpringSecurity

        定义类继承WebSecurityConfigurerAdapter并覆盖configure方法配置相关处理器,一以下是我的全部配置,token主要是通过上文编写的登录成功处理器生成并返回的,同时配置里也需要配置上过滤token需要的上文实现的token过滤器。

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                    .antMatchers(JWTConfig.antMatchers.split(",")).permitAll()
                    // 其他的需要登陆后才能访问  其他url都需要验证
                    .anyRequest().authenticated()
                .and()
                    //未登录处理逻辑
                    .httpBasic()
                    .authenticationEntryPoint(userAuthenticationEntryPointHandler)
                .and()
                    //表单登录相关配置
                    .formLogin()
                    //登录地址
                    .loginPage("/login")
                    //提交form表单的请求地址
                    .loginProcessingUrl("/security/login/myLoginForm")
                    .usernameParameter("username")
                    .passwordParameter("password")
                    .permitAll()
                    //登录成功处理
                    .successHandler(userLoginSuccessHandler)
                    //登录失败处理
                    .failureHandler(userLoginFailureHandler)
                .and()
                    //退出登录
                    .logout()
                    //退出登录路径
                    .logoutUrl("/security/logout")
                    //退出成功处理
                    .logoutSuccessHandler(userLogoutSuccessHandler)
                .and()
                    //开启跨域
                    .cors()
                .and()
                    //取消跨站请求伪造防护
                    .csrf().disable();

        // 基于Token不需要session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        // 禁用缓存
        http.headers().cacheControl();
        // 添加JWT过滤器
        http.addFilter(new JWTAuthenticationTokenFilter(authenticationManager()));
    }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值