Java实现登录与注销--JWT(2)

上文介绍了JWT的创建 与 拦截器的配置
这篇基于上篇文档jwt生成token后的在登录中的具体使用
(ps:因为我也是探索使用 所以陆续更新。有好的建议也欢迎指出)

首先拦截器和 JwtTokenUtil我重新放一个。可能会有些许区别
此篇文章加入了会话管理token的存储。当然你可以自己切换为redis管理。
因为隐私问题 不方便透漏包名。请自行添加 若少什么不会的东西 可以留言 我会提供


/**
 * @author chunying
 * @Date 2020.12.06
 * @Description 关于jwt的操作
 */
@Component
public class JwtTokenUtil {

    private static final Logger LOG = LoggerFactory.getLogger(JwtTokenUtil.class);


    //加密key
    private static String KEY = null;

    @Value("${token.encrypt.key}")
    public void setKey(String key) {
        KEY = key;
    }

    /**
     * 生成token 以用户基本信息为payload基础生成token 这样解析出来知道是哪个用户
     */
    public static String createToken(UserToken userToken)throws Exception {
        StringBuffer sbResult = new StringBuffer();
        JSONObject jwtFirstJson = new JSONObject();
        String jwtSecondStr = Base64Util.getEncodeStr(JSON.toJSONString(userToken), TokenConstant.FORMAT);
        //拼接jwt格式
        jwtFirstJson.put(TokenConstant.TYPE_KEY, TokenConstant.TYPE_VALUE);
        String jwtFirstStr = Base64Util.getEncodeStr(JSON.toJSONString(jwtFirstJson), TokenConstant.FORMAT);
        sbResult.append(jwtFirstStr).append(TokenConstant.SPOT).append(jwtSecondStr);
        String hexString = AesUtil.parseEncrypt(sbResult.toString(), KEY);
        //设置过期时间
//        setLocalUser(userToken);
//        String jwtString = TOKEN_PREFIX + JWT.create()
//                .withSubject(jsonString)
//                .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
//                .sign(AesUtil.parseEncrypt(jsonString, KEY));

        return sbResult.append(TokenConstant.SPOT).append(hexString).toString();
    }

    /**
     * 解析返回的token
     */
    public static UserToken verifyToken(String value) {
        if (StringUtils.isBlank(value)) {
            throw new JwtException(HttpCode.BAD_REQUEST, ErrorCode.TOKEN_FORMAT);
        }
        String[] valueArray = value.split("\\.");
        if (valueArray.length < 3) {
            throw new JwtException(HttpCode.BAD_REQUEST, ErrorCode.TOKEN_FORMAT);
        }
        String thirdStr = AesUtil.parseDesEncrypt(valueArray[2], KEY);
        //校验是否是真实用户?暂定先返回传过来的信息
        if (StringUtils.isBlank(thirdStr)) {
            throw new JwtException(HttpCode.BAD_REQUEST, ErrorCode.TOKEN_FORMAT);
        }
        String[] thirdDesStr = thirdStr.split("\\.");
        if (thirdDesStr == null || thirdDesStr.length < 2) {
            throw new JwtException(HttpCode.BAD_REQUEST, ErrorCode.TOKEN_CONTENT);
        }
        //校验是否符合格式
        try {
            //检验payload 与 header 是否被动过
            if (!valueArray[0].equals(thirdDesStr[0]) || !valueArray[1].equals(thirdDesStr[1])) {
                throw new JwtException(HttpCode.BAD_REQUEST, ErrorCode.TOKEN_MODIFIED);
            }
            //first str
            String firstStr = Base64Util.getDecodeStr(thirdDesStr[0], TokenConstant.FORMAT);
            JSONObject jsonObject = JSON.parseObject(firstStr);
            String typeValue = jsonObject.getString(TokenConstant.TYPE_KEY);
            if (!StringUtils.isNotBlank(typeValue) || !typeValue.equals(TokenConstant.TYPE_VALUE)) {
                throw new JwtException(HttpCode.BAD_REQUEST, ErrorCode.TOKEN_CONTENT);
            }
            //second str
            String secondStr = Base64Util.getDecodeStr(thirdDesStr[1], TokenConstant.FORMAT);
            UserToken userToken = JSON.parseObject(secondStr, UserToken.class);
//            if (isExpire(userToken)) {
//                throw new JwtException(ErrorCode.TOKEN_EXPIRE, "token is expire");
//            }
            return userToken;
        }catch(Exception e) {
            LOG.error(e.getMessage(), e);
            e.printStackTrace();
        }

        return null;
    }

    /**
     * TODO
     * 判断用户token是否过期
     * @param userToken
     * @return
     */
    @Deprecated
    public static Boolean isExpire(UserToken userToken) {
        return null;
    }

    /**
     * 刷新token生效时间
     * @param usertoken
     */
    public static void refreshExpireTime(HttpSession session, UserToken usertoken) {
        setLocalUser(session, usertoken);
    }

    /**
     * 将token放入本地线程缓存 可以知道当前线程用户信息
     * @param userToken
     */
    public static void setLocalUser(HttpSession session, UserToken userToken) {
        session.setAttribute(TokenConstant.SESSION_KEY, userToken);
    }

    /**
     * 校验session中用户信息是否过期
     * @param session
     * @param userToken
     * @return
     */
    public static Boolean validateSession(HttpSession session, UserToken userToken) {
        if (session == null) {
            return true;
        }
        Object attribute = session.getAttribute(TokenConstant.SESSION_KEY);
        return attribute == null;
    }

    /**
     * 获取当前session域的用户token信息
     * @return
     */
    public static UserToken getUserToken(HttpSession session) {
        return (UserToken)session.getAttribute(TokenConstant.SESSION_KEY);
    }

    /**
     * 清空当前session
     * @param session
     */
    public static void deleteSession(HttpSession session) {
        if (session == null) {
            return;
        }
        Enumeration<String> attributeNames = session.getAttributeNames();
        while (attributeNames.hasMoreElements()) {
            // 获取session的属性名称
            String name = attributeNames.nextElement();
            session.removeAttribute(name);
        }
    }

    /**
     * 获取当前请求会话中的用户信息
     * @return
     * @throws Exception
     */
    public static UserToken getUserTokenBySession() throws CommonException {
        HttpSession session = ServletUtil.getRequest().getSession(false);
        if (session == null) {
            throw new TokenException(HttpCode.FORBIDDEN, ErrorCode.TOKEN_EXPIRE);
        }
        UserToken userToken = getUserToken(session);
        return userToken;
    }
}

因为隐私问题 不方便透漏包名。请自行添加 若少什么不会的东西 可以留言 我会提供

/**
 * @author chunying
 * @Date 2020.12.07
 * @Descrip 全局方法拦截 若方法不想使用此拦截器 请看JwtIgnore
 */
@Component
public class AuthenticationInterceptor implements HandlerInterceptor {

    @Autowired
    private CommonPropertyUtil commonPropertyUtil;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Boolean tokenIsOpen = commonPropertyUtil.getTokenIsOpen();
        if (!tokenIsOpen) {
            return true;
        }
        //swagger页面的请求路径 放行
        // 由于SpringBoot异常处理机制 在发生系统错误时 ErrorPageCustomize 会发送/error请求进行处理
        // 为了程序正常抛出错误 放行此url
        String requestURI = request.getRequestURI();
        String[] swaggerUri={"/swagger-resources","/webjars/bycdao-ui/images/api.ico","/swagger-resources/configuration/ui", "/error"};
        for (String s : swaggerUri) {
            if(requestURI.startsWith(s)){
                return true;
            }
        }
        //从http请求头中取出token
        final String token = request.getHeader(TokenConstant.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;
            }
            return true;
        }
        if (StringUtils.isBlank(token)) {
            throw new TokenException(HttpCode.FORBIDDEN, ErrorCode.TOKEN_NULL);
        }

        //验证,并获取token内部信息
        UserToken userToken = JwtTokenUtil.verifyToken(token);
        //校验是否登录
        Boolean validateSession = JwtTokenUtil.validateSession(request.getSession(false), userToken);
        if (validateSession) {
            throw new TokenException(HttpCode.FORBIDDEN, ErrorCode.TOKEN_EXPIRE);
        }
        return true;
    }


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


因为这篇文章是改良后的
有一些异常的和取属性的东西放入了配置文件或者继承自己写的底层异常 我就不提供了 可以去上篇文章寻找旧版整合 或者依据各自需要各自修改。

注意 我这里导入了其他的工具类。如果你也想这么做 请将拦截器交给spring去管理 否则注入的类是null

说一下思路
我这里会校验三个部分
一、token是否存在
二、token是否符合我们的加密要求
三、登录会将token存入服务器会话session并设置过期时间 时间请自定义、每次会校验会话是否超时、是否存在token

以下是登录controller

@Api(tags = "用户登录相关")
@RestController
@PropertySource("classpath:application.yml")
public class UserLoginController {

    @Value("${login.error.maxCount:5}")
    private Integer maxLoginErrorCount;

    @Value("${token.session.expire:300}")
    private Integer tokenSessionMax;

    @Autowired
    private LoginService loginService;

    @Autowired
    private CommonPropertyUtil commonPropertyUtil;

    /**
     * 登录
     * @param
     * @return
     */
    @ApiOperation(value="用户登录")
    @JwtIgnore
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public Object login(@RequestBody LoginParamVO parameter, HttpServletRequest request,
                        HttpServletResponse response) {
        //查询 校验
        if (parameter.isNull()) {
            return ResultContent.error(HttpCode.BAD_REQUEST, ErrorCode.LOGIN_PARAM_NULL);
        }
        //解密密码
        String decryptPassword = AesUtil.decryptByKeyAndIV(parameter.getPassword());
        parameter.setPassword(decryptPassword);
        //判断登录用户是否存在
        SysUser sysUserByName = loginService.findSysUserByName(parameter);
        if (sysUserByName == null) {
            return ResultContent.error(HttpCode.BAD_REQUEST, ErrorCode.LOGIN_USERNAME_NOT_FOUND);
        }
        //校验密码
        Boolean validatePassword = loginService.validatePassword(parameter.getPassword(), sysUserByName);
        if (!validatePassword) {
            Integer loginErrorCount = sysUserByName.getLoginErrorCount();
            if (loginErrorCount > maxLoginErrorCount) {
                return ResultContent.error(HttpCode.BAD_REQUEST, ErrorCode.LOGIN_ERROR_MAX);
            }
            sysUserByName.setLoginErrorCount(loginErrorCount + 1);
            loginService.updateSysUserById(sysUserByName);
            return ResultContent.error(HttpCode.BAD_REQUEST, ErrorCode.LOGIN_PASSWORD_FAIL);
        }
        loginService.loginSuccess(sysUserByName);
        //生成token
        UserToken userToken = new UserToken(sysUserByName);
        String token = null;
        try {
            token = JwtTokenUtil.createToken(userToken);
            response.setHeader(TokenConstant.AUTH_HEADER_KEY, token);
        }catch(Exception e) {
            e.printStackTrace();
        }
        //放入session
        HttpSession session = request.getSession();
        session.setMaxInactiveInterval(tokenSessionMax);
        JwtTokenUtil.setLocalUser(session, userToken);
        ResultContent<Map> resultContent = new ResultContent<>();
        resultContent.setExtra("token", token);
        resultContent.successByExtra(resultContent);
        return resultContent;
    }

    /**
     * 登出|注销
     * @param
     * @param request
     * @return
     */
    @JwtIgnore
    @ApiOperation(value = "用户注销")
    @RequestMapping(value = "/logout", method = RequestMethod.POST)
    public Object logout(HttpServletRequest request) {
        // 清空session
        try {
            JwtTokenUtil.deleteSession(request.getSession());
        }catch(Exception e) {
            return ResultContent.error(HttpCode.INTERNAL_SERVER_ERROR, ErrorCode.UN_KNOWN_ERROR);
        }
        return ResultContent.success();
    }
}


Service
数据库存的是md5带盐值的密码
前端传输的是带key和偏移量IV的Aes加密key
后端拿到后需要先解密 然后按照盐值进行加密对比
具体加解密工具类 这个有很多 就不提供了哈 如果实在查不到需要的话请留言
或者等等下篇文章我会提供

@Service
public class LoginService {

   //mapper 我就不提供了
    @Autowired   
    private SysUserMapper sysUserMapper;

    /**
     * jiao
     * @param paramPassword
     * @param sysUser
     * @return
     */
    public Boolean validatePassword(String paramPassword, SysUser sysUser) {
        String salt = sysUser.getSalt();
        String encryptData = EncryptionUtil.encryptByMD5WithSalt(paramPassword, salt);
        return encryptData.equals(sysUser.getPassword());
    }

    public void loginSuccess(SysUser sysUser) {
        sysUser.setLoginCount(sysUser.getLoginCount() + 1);
        sysUser.setLastLoginTime(sysUser.getLoginTime());
        sysUser.setLoginTime(new Date());
        sysUser.setLoginErrorCount(0);
        updateSysUserById(sysUser);
    }

    public SysUser findSysUserByName(LoginParamVO parameter) {
        SysUser sysUser = new SysUser();
        sysUser.setUsername(parameter.getUsername());
        QueryWrapper<SysUser> queryWrapper = new QueryWrapper<>(sysUser);
        SysUser sysUserByUserName = sysUserMapper.selectOne(queryWrapper);
        return sysUserByUserName;
    }

    @Transactional(rollbackFor = Exception.class)
    public void updateSysUserById(SysUser sysUser) {
        sysUserMapper.updateById(sysUser);
    }
}


这篇文章提供了登录的基本思路 一些VO、mapper 和加解密并没有提供、太麻烦了。。。。这些根据自己需要适当修改或查询就行了。或者实在不会可以留言~

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
这是一个基于Spring Boot、Spring Security、JWT和OAuth2的示例项目,实现了用户注册、登录注销、刷新令牌、访问受保护资源等功能。 ## 技术栈 - Spring Boot 2.5.4 - Spring Security 5.5.1 - Spring Data JPA 2.5.4 - MySQL 8.0.26 - JWT 0.11.2 - OAuth2 2.5.4 - Lombok 1.18.20 ## 数据库配置 在MySQL数据库中新建一个名为`springboot_security_jwt_oauth2`的数据库,执行以下SQL语句创建用户表: ```sql CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `username` varchar(255) NOT NULL COMMENT '用户名', `password` varchar(255) NOT NULL COMMENT '密码', `enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用', `create_time` datetime NOT NULL COMMENT '创建时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; ``` ## 项目结构 ``` ├── src/main/java │ └── com │ └── example │ └── demo │ ├── DemoApplication.java │ ├── config │ │ ├── JwtConfig.java │ │ ├── MyPasswordEncoder.java │ │ └── SecurityConfig.java │ ├── controller │ │ ├── LoginController.java │ │ └── UserController.java │ ├── dao │ │ ├── UserRepository.java │ │ └── UserRoleRepository.java │ ├── entity │ │ ├── User.java │ │ └── UserRole.java │ ├── exception │ │ ├── JwtAuthenticationException.java │ │ └── UserNotFoundException.java │ ├── service │ │ ├── AuthService.java │ │ ├── UserService.java │ │ └── impl │ │ ├── AuthServiceImpl.java │ │ └── UserServiceImpl.java │ ├── util │ │ ├── JwtTokenUtil.java │ │ └── JwtUserDetailsService.java │ └── web │ ├── JwtAuthenticationEntryPoint.java │ ├── JwtAuthenticationFilter.java │ ├── JwtAuthorizationFilter.java │ ├── RestResponse.java │ └── UserNotFoundExceptionHandler.java └── src/main/resources ├── application.properties ├── static └── templates ``` - `config`:Spring Security和JWT的配置类 - `controller`:控制器类,处理请求和响应 - `dao`:数据访问层,使用Spring Data JPA实现 - `entity`:实体类 - `exception`:异常类 - `service`:服务层接口和实现类 - `util`:工具类,包括JWT生成和解析、用户认证等 - `web`:Web相关类,包括异常处理、JWT过滤器等 ## API文档 ### 用户注册 - URL:`/api/register` - Method:POST - Request: ```json { "username": "test", "password": "123456" } ``` - Response: ```json { "code": 200, "message": "注册成功", "data": { "id": 1, "username": "test", "password": "$2a$10$8uFJ3zZB.Sd7K3YB2K3Y/OfVhF4oJXeS3j0R2A3RG1c2UJWuXkSdC", "enabled": true, "createTime": "2021-10-01T08:16:28.000+00:00" } } ``` ### 用户登录 - URL:`/api/login` - Method:POST - Request: ```json { "username": "test", "password": "123456" } ``` - Response: ```json { "code": 200, "message": "登录成功", "data": { "accessToken": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0Iiwicm9sZXMiOiJST0xFX1VTRVIiLCJleHAiOjE2MzI5NjQwODh9.5Syf8x3CZaLl0yHrXyXjJ4Qz4jJnVR3S4yIDg6GQ6puknFkJ9QWgJzJ5pB0tZzHfrGz2K1VJvJkHrOjLUQJWzA", "tokenType": "Bearer", "expiresIn": 3600 } } ``` ### 用户注销 - URL:`/api/logout` - Method:POST - Request Header: ``` Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0Iiwicm9sZXMiOiJST0xFX1VTRVIiLCJleHAiOjE2MzI5NjQwODh9.5Syf8x3CZaLl0yHrXyXjJ4Qz4jJnVR3S4yIDg6GQ6puknFkJ9QWgJzJ5pB0tZzHfrGz2K1VJvJkHrOjLUQJWzA ``` - Response: ```json { "code": 200, "message": "注销成功" } ``` ### 刷新令牌 - URL:`/api/refresh` - Method:POST - Request Header: ``` Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0Iiwicm9sZXMiOiJST0xFX1VTRVIiLCJleHAiOjE2MzI5NjQwODh9.5Syf8x3CZaLl0yHrXyXjJ4Qz4jJnVR3S4yIDg6GQ6puknFkJ9QWgJzJ5pB0tZzHfrGz2K1VJvJkHrOjLUQJWzA ``` - Response: ```json { "code": 200, "message": "刷新令牌成功", "data": { "accessToken": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0Iiwicm9sZXMiOiJST0xFX1VTRVIiLCJleHAiOjE2MzI5NjQxMzQsImlhdCI6MTYzMjk2MDUzNH0.2hWq8dLJ7s9G6MqQ8Gg7kNvGzeOaJQFb4eBZ9RcB6N8lP3kglz8W_KXMh8r4oJZkzy5HOVZrB5YSEKNxZyY5lg", "tokenType": "Bearer", "expiresIn": 3600 } } ``` ### 获取当前用户信息 - URL:`/api/user/info` - Method:GET - Request Header: ``` Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0Iiwicm9sZXMiOiJST0xFX1VTRVIiLCJleHAiOjE2MzI5NjQxMzQsImlhdCI6MTYzMjk2MDUzNH0.2hWq8dLJ7s9G6MqQ8Gg7kNvGzeOaJQFb4eBZ9RcB6N8lP3kglz8W_KXMh8r4oJZkzy5HOVZrB5YSEKNxZyY5lg ``` - Response: ```json { "code": 200, "message": "获取用户信息成功", "data": { "id": 1, "username": "test", "password": null, "enabled": true, "createTime": "2021-10-01T08:16:28.000+00:00", "authorities": [ { "authority": "ROLE_USER" } ] } } ``` ### 获取所有用户信息 - URL:`/api/user/all` - Method:GET - Response: ```json { "code": 200, "message": "获取所有用户信息成功", "data": [ { "id": 1, "username": "test", "password": null, "enabled": true, "createTime": "2021-10-01T08:16:28.000+00:00", "authorities": [ { "authority": "ROLE_USER" } ] } ] } ``` ## 完整代码 完整代码请参考[GitHub](https://github.com/zhongshijun/springboot-security-jwt-oauth2)。
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值