JWT实现单点登录

JWT单点登录

1、表结构

CREATE TABLE `user` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键,用户ID',
  `username` varchar(255) NOT NULL COMMENT '用户名',
  `password` varchar(255) NOT NULL COMMENT '密码',
  `gender` int(1) DEFAULT NULL COMMENT '性别:0男,1女',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;


INSERT INTO `user` VALUES (1, 'admin', '123456', 1, '2023-11-03 12:14:16', NULL);
INSERT INTO `user` VALUES (2, 'test', '123456', 0, '2023-11-07 12:14:41', NULL);

2、依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.1</version>
    <relativePath/>
</parent>

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

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.39</version>
    </dependency>

    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.3.0</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.6.5</version>
    </dependency>

    <!-- Token生成与解析-->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>
</dependencies>

3、实体

User:用户实体

@Data
public class User {
    private Long id;
    private String username;
    private String password;
    private Integer gender;
    private Date createTime;
    private Date updateTime;
}

UserToken:token中存储的用户信息。因为token可以被解码,所以不要有敏感信息。

@Data
public class UserToken implements Serializable {
    private Long id;
    private String username;
    private Integer gender;
}

4、JwtTokenUtil

用于生成和解析token。

public class JwtTokenUtil {
    //定义token返回头部
    public static final String AUTH_HEADER_KEY = "Authorization";

    //token前缀
    public static final String TOKEN_PREFIX = "Bearer ";

    //签名密钥
    public static final String SECRET = "q3t6w9z$C&F)J@NcQfTjWnZr4u7x";

    //有效期默认为 2hour
    public static final Long EXPIRATION_TIME = 1000L*60*60*2;

    /**
     * 创建Token
     * @param content
     * @return
     */
    public static String createToken(String content) {
        return TOKEN_PREFIX + Jwts.builder()
                .setSubject(content)
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .signWith(SignatureAlgorithm.HS256, SECRET)
                .compact();
    }

    /**
     * 校验token
     * @param token
     * @return
     * @throws Exception
     */
    public static String verifyToken(String token) throws Exception {
        try {
            return Jwts.parser()
                    .setSigningKey(SECRET)
                    .parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
                    .getBody()
                    .getSubject();

        } catch (ExpiredJwtException e) {
            throw new Exception("token已失效,请重新登录", e);
        } catch (SignatureException e) {
            throw new Exception("token验证失败!", e);
        }
    }
}

5、WebContextUtil

ThreadLocal缓存已经登录的用户。

public class WebContextUtil {

    //本地线程缓存token
    private static ThreadLocal<String> local = new ThreadLocal<>();

    /**
     * 设置token
     * @param content
     */
    public static void setUserToken(String content) {
        removeUserToken();
        local.set(content);
    }

    /**
     * 获取token信息
     * @return
     */
    public static UserToken getUserToken() {
        if (local.get() != null) {
            UserToken userToken = JSONUtil.toBean(local.get(), UserToken.class);
            return userToken;
        }
        return null;
    }

    /**
     * 删除token信息
     */
    public static void removeUserToken() {
        if (local.get() != null) {
            local.remove();
        }
    }
}

6、AuthenticationInterceptor(token解析)

public class AuthenticationInterceptor implements HandlerInterceptor {

    /**
     * handler参数:可以是HandlerAdapter存在的任何类型。最常用的是利用HandlerMethod的RequestMappingHandlerAdapter。
     * 它可以是一个常规类、一个servlet,甚至是一个函数(当使用函数方法时)。
     * Spring Web Services还有一个实现以及Spring Integration。
     * Spring本身将支持:
     * HandlerMethod
     * HandlerFunction
     * Conroller
     * Servlet
     * HttpRequestHandler
     * WsdlDefinition (Spring Web Services)
     * XsdDefinition (Spring Web Services)
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //从http请求头中获取token
        String token = request.getHeader(JwtTokenUtil.AUTH_HEADER_KEY);
        //如果不是映射到方法,放行(可能是请求静态资源等)
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        //如果是方法探测,放行
        if (HttpMethod.OPTIONS.equals(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
            return true;
        }
        //如果方法有JwtIgnore注解,放行
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        if (method.isAnnotationPresent(JwtIgnore.class)) {
            JwtIgnore jwtIgnore = method.getAnnotation(JwtIgnore.class);
            if (jwtIgnore.value()) {
                return true;
            }
        }
        if (StrUtil.isBlank(token)) {
            throw new Exception("token为空,鉴权失败!");
        }
        //验证token
        String userToken = JwtTokenUtil.verifyToken(token);
        //放入本地缓存
        WebContextUtil.setUserToken(userToken);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //方法结束后,移除缓存的token
        WebContextUtil.removeUserToken();
    }
}

7、JwtIgnore

自定义注解,用于排除指定方法的token检验。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface JwtIgnore {
    boolean value() default true;
}

8、WebMvc配置

@Configuration
public class GlobalWebMvcConfig implements WebMvcConfigurer {

    /**
     * 跨域配置
     * @param registry
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 添加映射路径
        registry.addMapping("/**")
                // 放行哪些原始域
                .allowedOriginPatterns("*")
                // 是否发送Cookie信息
                .allowCredentials(true)
                // 放行哪些原始域(请求方式)
                .allowedMethods("*")
                // 放行哪些原始域(头部信息)
                .allowedHeaders("*")
                // 暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
                .exposedHeaders("Server","Content-Length", "Authorization", "Access-Token", "Access-Control-Allow-Origin","Access-Control-Allow-Credentials");
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //token拦截器
        registry.addInterceptor(new AuthenticationInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/static/**");
    }
}

9、UesrService

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public String login(String username, String password) throws Exception {
        if (StrUtil.isBlank(username) || StrUtil.isBlank(password)) {
            throw new Exception("用户名或密码为空!");
        }
        User user = getUserByName(username);
        if (!password.equals(user.getPassword())) {
            throw new Exception("密码错误!");
        }
        //创建token
        UserToken userToken = BeanUtil.copyProperties(user, UserToken.class);
        String token = JwtTokenUtil.createToken(JSONUtil.toJsonStr(userToken));
        return token;
    }


    public User getUserByName(String username) {
        return userMapper.getUserByName(username);
    }
}

10、UserController

@RestController
public class UserController {

    @Autowired
    private UserService userService;


    @JwtIgnore
    @PostMapping("/login")
    public Map<String, Object> login(@RequestParam("username")String username, @RequestParam("password")String password) throws Exception {
        Map<String, Object> result = new HashMap<>();
        String token = userService.login(username, password);
        result.put("token", token);
        return result;
    }


    @GetMapping("/test")
    public Map<String, Object> test() {
        Map<String, Object> result = new HashMap<>();
        result.put("msg", "success");
        result.put("data", WebContextUtil.getUserToken());
        return result;
    }

}

11、ExceptionController(全局异常处理)

@ControllerAdvice
public class ExceptionController {

    @ResponseBody
    @ExceptionHandler(Exception.class)
    public Map<String, Object> handleException(Exception e) {
        Map<String, Object> map = new HashMap<>();
        map.put("code", 401);
        map.put("message", e.getMessage());
        return map;
    }
}

12、测试

未登录直接访问:
在这里插入图片描述

登录:
在这里插入图片描述

登录后访问接口:
在这里插入图片描述

访问静态资源:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值