XuLingLing

Spring boot 简单实现jwt

电脑版发表于:2020/4/2 9:55


JWT 的原理


基于Token的身份验证用来替代传统的cookie+session身份验证方法中的session。

token应用流程为:


1、初次登录:用户初次登录,输入用户名密码。


2、密码验证:服务器从数据库取出用户名和密码进行验证。


3、生成JWT:服务器端验证通过,根据从数据库返回的信息,以及预设规则,生成JWT。


4、返还JWT:服务器的HTTP RESPONSE中将JWT返还。


5、带JWT的请求:以后客户端发起请求,HTTP REQUEST HEADER中的Authorization字段都要有值,为JWT,用来验证用户身份以及对路由,服务和资源的访问权限进行验证。请求验证的url可以例如:http://127.0.0.1:8083/change/goodsMenu? token=JWT


JWT 是jsonWebToken 简称,现在流行的前后端分离开发(jwt不止是运用于web应用,app,小程序,H5混合开发都可)基本上都使用jwt来验证用户token,减少了服务器session 的使用,也能减轻服务器的压力,是当下Java开发不二选择

更多的关于jwt的东西都可自行百度,这里就不多赘述


使用JWT

1、添加依赖

         <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.2.0</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

2、找到一个合适的Jwt加密工具

网上的加密工具都不一样,但达到的目的都是一样的

@Component
public class JwtTokenUtil {

    private static final long serialVersionUID = -3301605591108950415L;

    private String secret = ParamUtil.getValue("jwt_secret");

    private Long expiration = Long.valueOf(ParamUtil.getValue("jwt_expiration"));

    private String tokenHeader = ParamUtil.getValue("jwt_tokenHeader");

    private Clock clock = DefaultClock.INSTANCE;

    public String generateToken(UserInfo userInfo) {
        Map<String, Object> claims = new HashMap<>();
        return doGenerateToken(claims, userInfo.getName());
    }

    public String generateToken(UserInfo user, String userName) {
        Map<String, Object> claims = new HashMap<>();
        // 自定义参数,把需要存入token的参数都可以自定义到这里,比如 角色 部门类的关键参数
        claims.put("userId", user.getId());
        claims.put("phone", user.getName());
        return doGenerateToken(claims, userName);
    }

    private String doGenerateToken(Map<String, Object> claims, String subject) {
        final Date createdDate = clock.now();
        final Date expirationDate = calculateExpirationDate(createdDate);

        return Jwts.builder()
                .setClaims(claims) // 存入自定义参数
                .setSubject(subject)
                .setIssuedAt(createdDate)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    private Date calculateExpirationDate(Date createdDate) {
        return new Date(createdDate.getTime() + expiration);
    }
    public String getUsernameFromToken(String token) {
        return getClaimFromToken(token, Claims::getSubject);
    }

    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);
    }

    public Claims getAllClaimsFromToken(String token) {
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
    }


    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(clock.now());
    }

    public Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
    }
}

3、生产加密token,登录时返回

加密字符串可以得到了,接下来就是运用起来,在登录接口利用jwtTokenUtil 生成一个Token 并在登录接口一并返回

4、拦截器拦截并验证token

@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 如果不是映射到方法直接通过
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        String servletPath = request.getServletPath();
        if (methodAnnotation != null) {
            // 判断是否存在令牌信息,如果存在,则允许登录
            //token_access 这就是前端放在请求头中的jwtToken
            String accessToken = request.getHeader("token_access");
            if (ToolUtil.isEmpty(accessToken)) {
                //这里抛给前端的为一个自定义异常枚举,可自己定义
               //RRException 为继承 RuntimeException 的自定义枚举
                throw new RRException(RongRunErrorCodeEnum.SYSTEM_LOGIN);
            }
            try {
                
                Claims claims = jwtTokenUtil.getAllClaimsFromToken(accessToken);
                //通过jwt加密工具中自定义参数取出自己想要验证的值,来进行操作
                Long userId = Long.valueOf(claims.get("userId").toString());
                //这个可要可不要,通过这里自己在请求中加参数,可以让前端少传几个已经在token中自定义好的值
                request.setAttribute("userId", userId);
                //接下来可处理需要验证并且已经通过后的业务
                
            } catch (ExpiredJwtException e) {
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED,"token已过期");
                ServletOutputStream out = response.getOutputStream();
                OutputStreamWriter ow = new OutputStreamWriter(out, "utf-8");
                ow.write(JSONObject.toJSONString(ApiJson.returnNG("请登录!")));
                ow.flush();
                ow.close();
                return false;
            } catch (MalformedJwtException e) {
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED,"token有误");
                ServletOutputStream out = response.getOutputStream();
                OutputStreamWriter ow = new OutputStreamWriter(out, "utf-8");
                ow.write(JSONObject.toJSONString(ApiJson.returnNG("请登录!")));
                ow.flush();
                ow.close();
                return false;
            }
            return true;
    }

}

处理类已经完成,接下来是添加拦截器 自定义类并且实现WebMvcConfigurer

@Configuration
@Slf4j
public class MyInterceptor implements WebMvcConfigurer {
/*
    @Autowired
    private LoginInterceptor loginInterceptor;*/

    /**
     * 重写添加拦截器方法并添加配置拦截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor());//添加拦截规则
    }

    @Bean
    public LoginInterceptor loginInterceptor (){
        return new LoginInterceptor();
    }
}

到此就可以完成spring boot 对jwt的实现,也可以实现用户单点登录,这里就不细说。原理就是在用户登录时就在服务器中存入token,在拦截器中获取到并对比即可

* 另外说明,以上时全局拦截,如果有的同志只需要进行局部拦截的话,可以使用自定义注解的方式

Spring boot 中使用注解也时非常方便 如果有同志对注解不是很了解的话,可以百度一哈

1、创建注解类


2、使用注解


3、在拦截规则中使用注解

       Method method = handlerMethod.getMethod();
       // 通过获取注解的方法,判断有注解的方法则需要拦截验证
       YourAnnotation yourAnnotation = method.getAnnotation(YourAnnotation.class);
       // 有 @methodAnnotation 注解,需要认证
       if (yourAnnotation != null) {
           // 判断是否存在令牌信息,如果存在,则允许登录
           String accessToken = request.getHeader("token_access");
           if (ToolUtil.isEmpty(accessToken)) {
               throw new RRException(RongRunErrorCodeEnum.SYSTEM_LOGIN);
           }
           try {
               Claims claims = jwtTokenUtil.getAllClaimsFromToken(accessToken);
              
           } catch (ExpiredJwtException e) {
          }
          }

这样就可以只验证加了自定义注解的接口,实现局部拦截

关于TNBLOG
TNBLOG,技术分享。技术交流:群号677373950
ICP备案 :渝ICP备18016597号-1
App store Android
精彩评论
{{item.replyName}}
{{item.content}}
{{item.time}}
{{subpj.replyName}}
@{{subpj.beReplyName}}{{subpj.content}}
{{subpj.time}}
猜你喜欢