JavaEE:使用JWT生成/解析token,实现登录/验证token功能

说明:

JWT能创建/解析token,一般由客户端上传手机号与验证码,服务器生成token,返回token给客户端,客户端使用此token访问其他需要登录的接口。

SpringBoot配置:https://blog.csdn.net/a526001650a/article/details/106687559

一、利用JWT生成/解析token,实现登录功能:
1.导入JWT依赖:

<!-- 导入JWT依赖 -->
<dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt</artifactId>
      <version>0.9.1</version>
</dependency>
<!-- 配置ConfigurationProperties执行器 -->
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-configuration-processor</artifactId>
</dependency>

2.在application.yml中配置密钥与过期时间:

jwt:  #jwt配置,供JWTUtils类使用
  key: _yyh123=  #jwt生成token时的密钥
  expiration: 5000  #token 5秒超时

3.创建JWT工具类,用于生成/解析token:

//token工具类
@ConfigurationProperties(prefix = "jwt") //加载application.yml配置文件jwt节点的信息
public class JWTUtil {
    private String key; //密钥,名称要与application.yml中配的一样
    private long expiration; //过期时间,名称要与application.yml中配的一样
    public String getKey() {
        return key;
    }
    public void setKey(String key) {
        this.key = key;
    }
    public long getExpiration() {
        return expiration;
    }
    public void setExpiration(long expiration) {
        this.expiration = expiration;
    }
    //生成token
    public String genToken(String userNo, String phone) {
        long curTime = System.currentTimeMillis();
        JwtBuilder builder = Jwts.builder()
                .setId(userNo).setSubject(phone) //使用用户ID与手机号
                .setIssuedAt(new Date(curTime)) //设置token创建时间
                .signWith(SignatureAlgorithm.HS256, key); //进行签名,HS256方式,密钥为key
        if (expiration > 0) {
            builder.setExpiration(new Date(curTime + expiration));  //设置token过期时间
        }
        //builder.claim("key1", "value1");  //添加自定义key:value对
        return builder.compact(); //生成token
    }
    //解析token
    public Claims parser(String token) {
        Claims c = Jwts.parser().setSigningKey(key) //设置签名密钥为yyh
                .parseClaimsJws(token).getBody(); //解析token
        String userNo = c.getId(); //获取token中的用户ID
        String phone = c.getSubject(); //获取token中的手机号
        Date createDate = c.getIssuedAt(); //获取token的创建时间
        Date expirationDate = c.getExpiration(); //获取token的过期时间
        //String value1 = (String) c.get("key1"); //获取token中的自定义key:value
        return c;
    }
}

4.登录生成token:

(1)在启动类中配置JWTUtil的Bean,让能被扫描到:

//申明为引导类,类名自定义
@SpringBootApplication
public class JWTApplication {
    ...
    @Bean //类似<bean>配置,创建实例
    public JWTUtil jWTUtil() {
        return new JWTUtil();
    }
}

(2)登录生成并返回token:

@Controller
public class LoginController {
    @Autowired
    private JWTUtil jWTUtil;
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    @ResponseBody
    public String login(String phone, String code) {
        if (phone.equals("13000000000") && code.equals("123456")) { //登录成功的,生成token
            String userNo = "1"; //假如从库中查出用户ID
            return jWTUtil.genToken(userNo, phone); //根据userNo和手机号生成token
        }
        return "验证码错误";
    }
}

二、使用Spring拦截器拦截其他需要登录的接口:

Zuul拦截处理(在ZuulGatewayFilter类run方法中处理):JavaEE:SpringCloud-使用Zuul网关提供统一请求入口_a526001650a的专栏-CSDN博客

1.自定义拦截器类,继承HandlerInterceptorAdapter或实现HandlerInterceptor:

@Component
public class LoginFilter extends HandlerInterceptorAdapter { //登录拦截器,或实现HandlerInterceptor
    @Autowired
    private JWTUtil jWTUtil;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//在接口请求处理之前校验token
        String authHead = request.getHeader("Authorization");
        if (StringUtils.isEmpty(authHead) !authHead.startsWith("Bearer ")) {
            write(response, "10001", "没有登录");
            return false; //没有登录
        }
        String headToken = authHead.substring(7);
        try {
            Claims c = jWTUtil.parser(headToken); //调用JWTUtil工具类解析token
            String userNo = c.getId(); //从token中取userNo
            String redisToken = redisTemplate.opsForValue().get("TOKEN_" + userNo); //根据userNo从redis中取token
            if (StringUtils.isEmpty(redisToken) || !redisToken.equals(headToken)) { //将header中的token与redis中的token进行比较,一致就是合法,不一致就是不合法
                write(response, "10002", "非法登录");
                return false; //非法登录
            }
        } catch (Exception e) {//token过期时会报错
            e.printStackTrace();
            write(response, "10003", "token过期");
            return false; //token过期
        }
        return true; //已登录,让通过,访问实际接口
    }
    private void write(HttpServletResponse response, String code, String msg) {
        response.setCharacterEncoding("utf-8");
        response.setContentType("text/json");
        try (OutputStream out = response.getOutputStream()) {
            out.write(String.format("{\"code\": \"%s\", \"msg\": \"%s\"}", code, msg).getBytes("utf-8"));
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.配置拦截或不拦截的接口,使用自定义拦截器,继承WebMvcConfigurationSupport或WebMvcConfigurer:

@Configuration
public class LoginFilterConfig extends WebMvcConfigurationSupport { //拦截器的一些配置,或继承WebMvcConfigurer
    @Autowired
    private LoginFilter loginFilter;
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry r) {//映射静态资源
        r.addResourceHandler("/**")
                .addResourceLocations("classpath:/META-INF/resources/")
                .addResourceLocations("file:/workspaces/images/");
    }
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {//配置拦截或不拦截的接口
        registry.addInterceptor(loginFilter)  //加入自定义的拦截器类
                .addPathPatterns("/**")  //拦截所有接口
                .excludePathPatterns("/login/*"); //对登录接口放行
    }
}

三、单点登录实现:

1.拦截器实现:

见上章LoginFilter

2.配置拦截器:

见上章LoginFilterConfig

3.登录/登出实现:

(1)单点登录(创建ticket):

@PostMapping("/singleLogin")  //单点登录-创建ticket接口,供子系统统一登录
public String singleLogin(String phone, String code, String redirectUrl, Model model, HttpServletRequest request, HttpServletResponse response) {
    if (StringUtils.isEmpty(phone) || StringUtils.isEmpty(code)) {
        model.addAttribute("msg", "手机号或验证码不能为空");
        return "login";
    }
    if (false) {
        model.addAttribute("msg", "验证码错误");
        return "login";
    }
    //1.根据手机号从库中查出用户信息
    User user = new User();
    //2.使用userNo为key,将用户信息缓存到redis中
    redisTemplate.opsForValue().set("USER_" + user.getUserNo(), gson.toJson(user));
    //3.生成全局登录标识,并缓存到redis+cookie中
    Cookie c = new Cookie("TICKET", genTicket(user.getUserNo()));
    c.setDomain("yyh.com");
    c.setPath("/");
    response.addCookie(c);
    //4.生成临时登录标识(并缓存到redis中,5分钟后过期),并定向跳回登录之前的页面
    return String.format("redirect:%s?tempTicket=%s", redirectUrl, genTempTicket());
}

(2)验证ticket:

@PostMapping("/verifyTicket")  //单点登录验证ticket接口
@ResponseBody
public Response verifyTicket(String tempLoginId, Model model, HttpServletRequest request, HttpServletResponse response) {
    //1.查询redis中是否有登录标识
    String redisTempLoginId = redisTemplate.opsForValue().get("TEMP_TICKET_" + tempLoginId);
    if (StringUtils.isEmpty(redisTempLoginId)) {
        return new Response("10004", "登录标识异常");
    }
    redisTemplate.delete("TEMP_TICKET_" + tempLoginId);
    //2.从cookie中取出全局登录标识,根据全局登录标识从redis中取取userNo
    String ticket = getTicketByCookie(request);
    //3.根据全局登录标识从redis中取取userNo
    String userNo = redisTemplate.opsForValue().get("TICKET_" + ticket);
    if (StringUtils.isEmpty(userNo)) {
        return new Response("10004", "登录标识异常");
    }
    //4.根据userNo从redis中取取user信息json
    String userJson = redisTemplate.opsForValue().get("USER_" + userNo);
    if (StringUtils.isEmpty(userNo)) {
        return new Response("10004", "登录标识异常");
    }
    return new Response("200", "登录标识验证通过", gson.fromJson(userJson, User.class));
}

(3)子系统二次登录:

@PostMapping("/login")  //登录
public String login(String redirectUrl, Model model, HttpServletRequest request, HttpServletResponse response) {
    model.addAttribute("redirectUrl", redirectUrl);
    //1.获取全局ticket,能获取到,则生成临时ticket,并重定向到原网页(登录成功)
    String ticket = getTicketByCookie(request);
    if (verifyTicket(ticket)) {
        return String.format("redirect:%s?tempTicket=%s", redirectUrl, genTempTicket());   //生成临时ticket,并重定向到原网页
    }
    //2.首次登录,则跳到单点登录-创建ticket
    return "singleLogin";
}

(4)登出:

@PostMapping("/logout")
public String logout(String userNo, HttpServletRequest request, HttpServletResponse response) {
    //1.清除redis中的ticket
    redisTemplate.delete("TICKET_" + getTicketByCookie(request));
    //2.清除redis中用户信息
    redisTemplate.delete("USER_" + userNo);
    //3.清除cookie中ticket
    delTicketByCookie(response);
    return "login";
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值