权限管理后端篇(七)之整合JWT实现token认证

Springboot整合jwt

pom依赖

 <!--引入jwt-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.19.2</version>
        </dependency>
        <!--使用mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>
        <!--使用lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--引入druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.9</version>
        </dependency>
        <!--使用sql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

工具类


import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
 
import java.util.Calendar;
import java.util.Map;
 
public class JWTUtils {
private static String TOKEN="token!QM3#e4r";
/*
* 生成token
* */
public static String getToken(Map<String,String>map){
    JWTCreator.Builder builder = JWT.create();
    map.forEach((k,v)->{
        builder.withClaim(k,v);
    });
    Calendar instance = Calendar.getInstance();
    instance.add(Calendar.DATE,7);//默认7天过期
    builder.withExpiresAt(instance.getTime());
    return builder.sign(Algorithm.HMAC256(TOKEN)).toString();
}
 
    /*
     * 生成token写法2
     * */
  /*  public static String getToken1(Map<String,String>map){
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.DATE,7);//默认7天过期
        //创建jwt builder
        JWTCreator.Builder builder = JWT.create();
        //payload
        map.forEach((k,v)->{
            builder.withClaim(k,v);
        });
       String token= builder.withExpiresAt(instance.getTime())
                .sign(Algorithm.HMAC256(TOKEN));
       return token;
    }*/
 
/*
* 验证token
* 原本写法
*  public static void verity(String token){
     JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
 }
* */
//优化验证token+获取token中payload
    public static DecodedJWT verity(String token){
        return JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
    }
 
 /*
 *获取token中payload
 * */
/* public static DecodedJWT getToken(String token){
     return JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
 }*/
 
}

 增加回响token


import com.xcuin.jwt.entity.User;
import com.xcuin.jwt.service.UserService;
import com.xcuin.jwt.utils.JWTUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.HashMap;
import java.util.Map;
 
@RestController
@Slf4j
public class UserController {
 
    @Autowired
    private UserService userService;
 
    @GetMapping("/user/login")
    public Map<String,Object>login(User user){
        log.info("用户名:{{}}",user.getName());
        log.info("用户名:{{}}",user.getPassword());
        HashMap<String, Object> map = new HashMap<>();
        try {
            User login = userService.login(user);
            HashMap<String, String> payload = new HashMap<>();
            payload.put("id",login.getId());
            payload.put("name",login.getName());
            //生成jwt的令牌
            String token = JWTUtils.getToken(payload);
            map.put("state",true);
            map.put("msg","认证成功");
            map.put("token",token);
        } catch (Exception e) {
            map.put("state",false);
            map.put("msg",e.getMessage());
        }
        return map;
    }
}

新增加拦截器


import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xcuin.jwt.utils.JWTUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
 
/**
 * 定义拦截器
 * @Date: 2022/04/14 12:26
 * @Version 1.0
 **/
@Component
public class JWTInterceptor implements HandlerInterceptor {
 
 @Override
 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
 
     HashMap<String, Object> map = new HashMap<>();
 
     //获取请求头中的令牌
     String token = request.getHeader("token");
 
   try {
    JWTUtils.verity(token);
   return  true;//放行请求
    /*
   常见异常
   SignatureVerificationException: 签名不一致异常
   TokenExpiredException: 令牌过期异常
   AlgorithmMismatchException: 算法不匹配异常
   */
   } catch (SignatureVerificationException e) {
   e.printStackTrace();
    map.put("msg","无效签名");
   } catch (TokenExpiredException e) {
       e.printStackTrace();
       map.put("msg","token过期");
   } catch (AlgorithmMismatchException e){
       e.printStackTrace();
       map.put("msg","token算法不一致");
   }catch (Exception e){
       e.printStackTrace();
       map.put("msg","token无效");
   }
   map.put("state",false);//设置状态
    //将map转为json jackjon
     String json = new ObjectMapper().writeValueAsString(map);
     response.setContentType("application/json;charset=UTF-8");
     response.getWriter().println(json);
     return false;
  }
 
 
 
}

配置拦截器:新建InterceptorConfig类 

import com.xcuin.jwt.interceptor.JWTInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
 
     @Override
    public void addInterceptors(InterceptorRegistry registry) {
      registry.addInterceptor(new JWTInterceptor())  // 注册拦截器
      .addPathPatterns("/**")   // 设置需要拦截的请求(也可设置成list集合)
      .excludePathPatterns("/**");  // 设置不需要拦截的请求(也可设置成list集合)
 
       }
    }

扩展:
工具类的编写

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
 
import java.io.Serializable;
import java.util.Calendar;
import java.util.Date;
 
/**
 * @author Lehr
 * @create: 2020-02-04
 */
public class JwtUtils {
 
    /**
     签发对象:这个用户的id
     签发时间:现在
     有效时间:30分钟
     载荷内容:暂时设计为:这个人的名字,这个人的昵称
     加密密钥:这个人的id加上一串字符串
     */
    public static String createToken(String userId,String realName, String userName) {
 
        Calendar nowTime = Calendar.getInstance();
        nowTime.add(Calendar.MINUTE,30);
        Date expiresDate = nowTime.getTime();
 
        return JWT.create().withAudience(userId)   //签发对象
                .withIssuedAt(new Date())    //发行时间
                .withExpiresAt(expiresDate)  //有效时间
                .withClaim("userName", userName)    //载荷,随便写几个都可以
                .withClaim("realName", realName)
                .sign(Algorithm.HMAC256(userId+"HelloLehr"));   //加密
    }
 
    /**
     * 检验合法性,其中secret参数就应该传入的是用户的id
     * @param token
     * @throws TokenUnavailable
     */
    public static void verifyToken(String token, String secret) throws TokenUnavailable {
        DecodedJWT jwt = null;
        try {
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret+"HelloLehr")).build();
            jwt = verifier.verify(token);
        } catch (Exception e) {
            //效验失败
            //这里抛出的异常是我自定义的一个异常,你也可以写成别的
            throw new TokenUnavailable();
        }
    }
 
    /**
    * 获取签发对象
    */
    public static String getAudience(String token) throws TokenUnavailable {
        String audience = null;
        try {
            audience = JWT.decode(token).getAudience().get(0);
        } catch (JWTDecodeException j) {
            //这里是token解析失败
            throw new TokenUnavailable();
        }
        return audience;
    }
 
 
    /**
    * 通过载荷名字获取载荷的值
    */
    public static Claim getClaimByName(String token, String name){
        return JWT.decode(token).getClaim(name);
    }
}

注解类的编写

在controller层上的每个方法上,可以使用这些注解,来决定访问这个方法是否需要携带token,由于默认是全部检查,所以对于某些特殊接口需要有免验证注解

免验证注解

@PassToken:跳过验证,通常是入口方法上用这个,比如登录接口

import java.lang.annotation.ElementType;
 
import java.lang.annotation.Retention;
 
import java.lang.annotation.RetentionPolicy;
 
import java.lang.annotation.Target;
 
 
 
/**
 * @author Lehr
 * @create: 2020-02-03
 */
 
@Target({ElementType.METHOD, ElementType.TYPE})
 
@Retention(RetentionPolicy.RUNTIME)
 
public @interface PassToken {
 
    boolean required() default true;
 
}

拦截器的编写

import org.springframework.context.annotation.Bean;
 
import org.springframework.context.annotation.Configuration;
 
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
 
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
 
 
/**
* @author lehr
*/
 
@Configuration
 
public class JwtInterceptorConfig implements WebMvcConfigurer {
 
   @Override
 
   public void addInterceptors(InterceptorRegistry registry) {
       //默认拦截所有路径
       registry.addInterceptor(authenticationInterceptor())
               .addPathPatterns("/**");
   }
 
   @Bean
   public JwtAuthenticationInterceptor authenticationInterceptor() {
       return new JwtAuthenticationInterceptor();
   }
} 

配置拦截器:新建InterceptorConfig类

import com.auth0.jwt.interfaces.Claim;
import com.imlehr.internship.annotation.PassToken; 
import com.imlehr.internship.dto.AccountDTO; 
import com.imlehr.internship.exception.NeedToLogin; 
import com.imlehr.internship.exception.UserNotExist; 
import com.imlehr.internship.service.AccountService; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.web.method.HandlerMethod; 
import org.springframework.web.servlet.HandlerInterceptor; 
import org.springframework.web.servlet.ModelAndView;   
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletResponse; 
import java.lang.reflect.Method; 
import java.util.Map;
 
 
 
/**
 * @author Lehr
 * @create: 2020-02-03
 */
 
public class JwtAuthenticationInterceptor implements HandlerInterceptor {
 
    @Autowired
 
    AccountService accountService;
 
 
 
    @Override
 
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
 
        // 从请求头中取出 token  这里需要和前端约定好把jwt放到请求头一个叫token的地方
 
        String token = httpServletRequest.getHeader("token");
 
        // 如果不是映射到方法直接通过
 
        if (!(object instanceof HandlerMethod)) {
 
            return true;
 
        }
 
        HandlerMethod handlerMethod = (HandlerMethod) object;
        Method method = handlerMethod.getMethod();
        //检查是否有passtoken注释,有则跳过认证
        if (method.isAnnotationPresent(PassToken.class)) {
            PassToken passToken = method.getAnnotation(PassToken.class);
            if (passToken.required()) {
                return true;
            }
        }
        //默认全部检查
        else {
            System.out.println("被jwt拦截需要验证");
            // 执行认证
            if (token == null) {
                //这里其实是登录失效,没token了   这个错误也是我自定义的,读者需要自己修改
                throw new NeedToLogin();
 
            }
 
            // 获取 token 中的 user Name
            String userId = JwtUtils.getAudience(token);
            //找找看是否有这个user   因为我们需要检查用户是否存在,读者可以自行修改逻辑
            AccountDTO user = accountService.getByUserName(userId);
            if (user == null) {
                //这个错误也是我自定义的
                throw new UserNotExist();
            }
 
            // 验证 token
            JwtUtils.verifyToken(token, userId)  
            //获取载荷内容
            String userName = JwtUtils.getClaimByName(token, "userName").asString();
            String realName = JwtUtils.getClaimByName(token, "realName").asString();
            //放入attribute以便后面调用
            request.setAttribute("userName", userName);
            request.setAttribute("realName", realName);
            return true;
        }
        return true;
    }
 
 
 
    @Override
    public void postHandle(HttpServletRequest httpServletRequest,
                           HttpServletResponse httpServletResponse,
                           Object o, ModelAndView modelAndView) throws Exception {

    }
 
 
 
    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest,
                                HttpServletResponse httpServletResponse,
                                Object o, Exception e) throws Exception {
    }
}

这段代码的执行逻辑大概是这样的:

目标方法是否有注解?如果有PassToken的话就不用执行后面的验证直接放行,不然全部需要验证
开始验证:有没有token?没有?那么返回错误
从token的audience中获取签发对象,查看是否有这个用户(有可能客户端造假,有可能这个用户的账户被冻结了),查看用户的逻辑就是调用Service方法直接比对即可
检验Jwt的有效性,如果无效或者过期了就返回错误
Jwt有效性检验成功:把Jwt的载荷内容获取到,可以在接下来的controller层中直接使用了(具体使用方法看后面的代码)
接口的编写
这里设计了两个接口:登录和查询名字,来模拟一个迷你业务,其中后者需要登录之后才能使用,大致流程如下:

登录代码

/**
     * 用户登录:获取账号密码并登录,如果不对就报错,对了就返回用户的登录信息
     * 同时生成jwt返回给用户
     *
     * @return
     * @throws LoginFailed  这个LoginFailed也是我自定义的
     */
 
    @PassToken 
    @GetMapping(value = "/login") 
    public AccountVO login(String userName, String password) throws LoginFailed{
        try{
            service.login(userName,password);
        }
        catch (AuthenticationException e)
        {
            throw new LoginFailed();
 
        }
        //如果成功了,聚合需要返回的信息
        AccountVO account = accountService.getAccountByUserName(userName);
        //给分配一个token 然后返回
        String jwtToken = JwtUtils.createToken(account);
        //我的处理方式是把token放到accountVO里去了
        account.setToken(jwtToken);
        return account; 
    }

业务代码

这里列举一个需要登录,用来测试用户名字的接口(其中用户的名字来源于jwt的载荷部分)

@GetMapping(value = "/username")
     public String checkName(HttpServletRequest req) {
         //之前在拦截器里设置好的名字现在可以取出来直接用了
         String name = (String) req.getAttribute("userName");
         return name;
     }

扩展2:
引入依赖jar:

<!-- JWT依赖 -->
<dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>3.4.1</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>

添加请求拦截器WebConfig(实现WebMvcConfigurer重写addInterceptors),对请求url进行token认证

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Resource
    private JwtFilter jwtFilter ;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtFilter).addPathPatterns("/**");
    }
}

新增拦截器JwtFilter

@Component
public class JwtFilter extends HandlerInterceptorAdapter {
 
    public static final String LOGIN_URL = "/login";
 
    @Resource
    private JwtTokenUtil jwtTokenUtil;
 
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws SignatureException {
        String uri = request.getRequestURI();
        if(uri.contains(LOGIN_URL) || uri.contains("/doc.html") || uri.contains("/swagger-resources") ){
            return true;
        }
        //获取token
        String token = request.getHeader(jwtTokenUtil.header);
        if(StringUtils.isEmpty(token)){
            token = request.getParameter(jwtTokenUtil.header);
        }
        if(StringUtils.isEmpty(token)){
            throw new SignatureException(jwtTokenUtil.header+"不能为空");
        }
 
        //判断token是否超时
        Claims claims = jwtTokenUtil.getTokenClaim(token);
        if(null == claims || jwtTokenUtil.isTokenExpired(claims.getExpiration())){
            throw new SignatureException(jwtTokenUtil.header+"失效,请重新登录");
        }
        return true;
    }
}

异常处理类,springAop实现(异常通知)

@RestControllerAdvice
public class SecurityExceptionHandler {
    @ExceptionHandler(value = {SignatureException.class})
    public Result authorizationException(SignatureException e){
        return Result.failedWith(null,CodeEnum.FORBBIDEN.getCode(),"权限不足");
    }
}
tokenUtil工具类

@Component
public class JwtTokenUtil {
    @Value("${jwt.secret}")
    public String secret;
    @Value("${jwt.expire}")
    public int expire;
    @Value("${jwt.header}")
    public String header;
 
    /**
     * 生成token
     * @param subject
     * @return
     */
    public String createToken (String subject){
        Date nowDate = new Date();
        Date expireDate = new Date(nowDate.getTime() + expire * 1000);
        return Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setSubject(subject)
                .setIssuedAt(nowDate)
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }
    /**
     * 获取token中注册信息
     * @param token
     * @return
     */
    public Claims getTokenClaim (String token) {
        try {
            return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        }catch (Exception e){
            return null;
        }
    }
    /**
     * 验证token是否过期失效
     * @param expirationTime
     * @return
     */
    public boolean isTokenExpired (Date expirationTime) {
        return expirationTime.before(new Date());
    }
 
    /**
     * 获取token失效时间
     * @param token
     * @return
     */
    public Date getExpirationDateFromToken(String token) {
        return getTokenClaim(token).getExpiration();
    }
    /**
     * 获取用户名从token中
     */
    public String getUsernameFromToken(String token) {
        return getTokenClaim(token).getSubject();
    }
 
    /**
     * 获取jwt发布时间
     */
    public Date getIssuedAtDateFromToken(String token) {
        return getTokenClaim(token).getIssuedAt();
    }
}

类中使用配置添加,application.yml中增加token的有效时间等

jwt:
  # 加密密钥
  secret: abcdefg1234567
  # token有效时长
  expire: 3600
  # header 名称
  header: token

jwt生成token使用

@Resource
JwtTokenUtil jwtTokenUtil;
 
@Override
public String login(String userName, String password) {
    //验证用户名密码
    ......
    //生成token
    return jwtTokenUtil.createToken("admin");
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值