springboot应用Oauth2.0授权框架

前言:所有代码来源于mall4j开源版本(gtiee https://gitee.com/gz-yami/mall4j?utm_source=alading&utm_campaign=repo),仅供学习使用,详细代码请看源代码。

一、token存储处理类TokenStore;

  1.定义了生成accessToken和refreshToken的方法;

  2.根据accessToken 获取用户信息的方法

  3.刷新token,并返回新的token的方法

  4.删除用户全部token的方法


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.symmetric.AES;
import com.yami.shop.common.constants.OauthCacheNames;
import com.yami.shop.common.enums.YamiHttpStatus;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.serializer.redis.KryoRedisSerializer;
import com.yami.shop.common.util.PrincipalUtil;
import com.yami.shop.security.common.bo.TokenInfoBO;
import com.yami.shop.security.common.bo.UserInfoInTokenBO;
import com.yami.shop.security.common.enums.SysTypeEnum;
import com.yami.shop.security.common.vo.TokenInfoVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;


/*
* * token管理 1. 登陆返回token 2. 刷新token 3. 清除用户过去token 4. 校验token * * @author FrozenWatermelon * @date 2020/7/2 */ @Component public class TokenStore { private static final Logger logger = LoggerFactory.getLogger(TokenStore.class); /** * 用于aes签名的key,16位 */ @Value("${auth.token.signKey:-mall4j--mall4j-}") public String tokenSignKey; private final RedisTemplate<String, Object> redisTemplate; private final RedisSerializer<Object> redisSerializer; private final StringRedisTemplate stringRedisTemplate; public TokenStore(RedisTemplate<String, Object> redisTemplate, StringRedisTemplate stringRedisTemplate) { this.redisTemplate = redisTemplate; this.redisSerializer = new KryoRedisSerializer<>(); this.stringRedisTemplate = stringRedisTemplate; } /** * 将用户的部分信息存储在token中,并返回token信息 * @param userInfoInToken 用户在token中的信息 * @return token信息 */ public TokenInfoBO storeAccessToken(UserInfoInTokenBO userInfoInToken) { TokenInfoBO tokenInfoBO = new TokenInfoBO(); String accessToken = IdUtil.simpleUUID(); String refreshToken = IdUtil.simpleUUID(); tokenInfoBO.setUserInfoInToken(userInfoInToken); tokenInfoBO.setExpiresIn(getExpiresIn(userInfoInToken.getSysType())); String uidToAccessKeyStr = getUserIdToAccessKey(getApprovalKey(userInfoInToken)); String accessKeyStr = getAccessKey(accessToken); String refreshToAccessKeyStr = getRefreshToAccessKey(refreshToken); // 一个用户会登陆很多次,每次登陆的token都会存在 uid_to_access里面 // 但是每次保存都会更新这个key的时间,而key里面的token有可能会过期,过期就要移除掉 List<byte[]> existsAccessTokensBytes = new ArrayList<>(); // 新的token数据 existsAccessTokensBytes.add((accessToken + StrUtil.COLON + refreshToken).getBytes(StandardCharsets.UTF_8)); Long size = redisTemplate.opsForSet().size(uidToAccessKeyStr); if (size != null && size != 0) { // List<String> tokenInfoBoList = stringRedisTemplate.opsForSet().pop(uidToAccessKeyStr, size); List<String> tokenInfoBoList = new ArrayList<>(); for (int i = 0; i < size; i++) { String s = stringRedisTemplate.opsForSet().pop(uidToAccessKeyStr); tokenInfoBoList.add(s); } if (tokenInfoBoList.size() > 0) { for (String accessTokenWithRefreshToken : tokenInfoBoList) { String[] accessTokenWithRefreshTokenArr = accessTokenWithRefreshToken.split(StrUtil.COLON); String accessTokenData = accessTokenWithRefreshTokenArr[0]; if (BooleanUtil.isTrue(stringRedisTemplate.hasKey(getAccessKey(accessTokenData)))) { existsAccessTokensBytes.add(accessTokenWithRefreshToken.getBytes(StandardCharsets.UTF_8)); } } } } redisTemplate.executePipelined((RedisCallback<Object>) connection -> { long expiresIn = tokenInfoBO.getExpiresIn(); byte[] uidKey = uidToAccessKeyStr.getBytes(StandardCharsets.UTF_8); byte[] refreshKey = refreshToAccessKeyStr.getBytes(StandardCharsets.UTF_8); byte[] accessKey = accessKeyStr.getBytes(StandardCharsets.UTF_8); connection.sAdd(uidKey, ArrayUtil.toArray(existsAccessTokensBytes, byte[].class)); // 通过uid + sysType 保存access_token,当需要禁用用户的时候,可以根据uid + sysType 禁用用户 connection.expire(uidKey, expiresIn); // 通过refresh_token获取用户的access_token从而刷新token connection.setEx(refreshKey, expiresIn, accessToken.getBytes(StandardCharsets.UTF_8)); // 通过access_token保存用户的租户id,用户id,uid connection.setEx(accessKey, expiresIn, Objects.requireNonNull(redisSerializer.serialize(userInfoInToken))); return null; }); // 返回给前端是加密的token tokenInfoBO.setAccessToken(encryptToken(accessToken,userInfoInToken.getSysType())); tokenInfoBO.setRefreshToken(encryptToken(refreshToken,userInfoInToken.getSysType())); return tokenInfoBO; } private int getExpiresIn(int sysType) { // 3600秒 int expiresIn = 3600; // 普通用户token过期时间 1小时 if (Objects.equals(sysType, SysTypeEnum.ORDINARY.value())) { expiresIn = expiresIn * 24 * 30; } // 系统管理员的token过期时间 2小时 if (Objects.equals(sysType, SysTypeEnum.ADMIN.value())) { expiresIn = expiresIn * 24 * 30; } return expiresIn; } /** * 根据accessToken 获取用户信息 * @param accessToken accessToken * @param needDecrypt 是否需要解密 * @return 用户信息 */ public UserInfoInTokenBO getUserInfoByAccessToken(String accessToken, boolean needDecrypt) { if (StrUtil.isBlank(accessToken)) { throw new YamiShopBindException(YamiHttpStatus.UNAUTHORIZED,"accessToken is blank"); } String realAccessToken; if (needDecrypt) { realAccessToken = decryptToken(accessToken); } else { realAccessToken = accessToken; } UserInfoInTokenBO userInfoInTokenBO = (UserInfoInTokenBO) redisTemplate.opsForValue() .get(getAccessKey(realAccessToken)); if (userInfoInTokenBO == null) { throw new YamiShopBindException(YamiHttpStatus.UNAUTHORIZED,"accessToken 已过期"); } return userInfoInTokenBO; } /** * 刷新token,并返回新的token * @param refreshToken * @return */ public TokenInfoBO refreshToken(String refreshToken) { if (StrUtil.isBlank(refreshToken)) { throw new YamiShopBindException(YamiHttpStatus.UNAUTHORIZED,"refreshToken is blank"); } String realRefreshToken = decryptToken(refreshToken); String accessToken = stringRedisTemplate.opsForValue().get(getRefreshToAccessKey(realRefreshToken)); if (StrUtil.isBlank(accessToken)) { throw new YamiShopBindException(YamiHttpStatus.UNAUTHORIZED,"refreshToken 已过期"); } UserInfoInTokenBO userInfoInTokenBO = getUserInfoByAccessToken(accessToken, false); // 删除旧的refresh_token stringRedisTemplate.delete(getRefreshToAccessKey(realRefreshToken)); // 删除旧的access_token stringRedisTemplate.delete(getAccessKey(accessToken)); // 保存一份新的token return storeAccessToken(userInfoInTokenBO); } /** * 删除全部的token */ public void deleteAllToken(String sysType, String userId) { String uidKey = getUserIdToAccessKey(getApprovalKey(sysType, userId)); Long size = redisTemplate.opsForSet().size(uidKey); if (size == null || size == 0) { return; } List<String> tokenInfoBoList = stringRedisTemplate.opsForSet().pop(uidKey, size); if (CollUtil.isEmpty(tokenInfoBoList)) { return; } for (String accessTokenWithRefreshToken : tokenInfoBoList) { String[] accessTokenWithRefreshTokenArr = accessTokenWithRefreshToken.split(StrUtil.COLON); String accessToken = accessTokenWithRefreshTokenArr[0]; String refreshToken = accessTokenWithRefreshTokenArr[1]; redisTemplate.delete(getRefreshToAccessKey(refreshToken)); redisTemplate.delete(getAccessKey(accessToken)); } redisTemplate.delete(uidKey); } private static String getApprovalKey(UserInfoInTokenBO userInfoInToken) { return getApprovalKey(userInfoInToken.getSysType().toString(), userInfoInToken.getUserId()); } private static String getApprovalKey(String sysType, String userId) { return userId == null? sysType : sysType + StrUtil.COLON + userId; } private String encryptToken(String accessToken,Integer sysType) { AES aes = new AES(tokenSignKey.getBytes(StandardCharsets.UTF_8)); return aes.encryptBase64(accessToken + System.currentTimeMillis() + sysType); } private String decryptToken(String data) { AES aes = new AES(tokenSignKey.getBytes(StandardCharsets.UTF_8)); String decryptStr; String decryptToken; try { decryptStr = aes.decryptStr(data); decryptToken = decryptStr.substring(0,32); // 创建token的时间,token使用时效性,防止攻击者通过一堆的尝试找到aes的密码,虽然aes是目前几乎最好的加密算法 long createTokenTime = Long.parseLong(decryptStr.substring(32,45)); // 系统类型 int sysType = Integer.parseInt(decryptStr.substring(45)); // token的过期时间 int expiresIn = getExpiresIn(sysType); long second = 1000L; if (System.currentTimeMillis() - createTokenTime > expiresIn * second) { throw new YamiShopBindException(YamiHttpStatus.UNAUTHORIZED,"token error"); } } catch (Exception e) { throw new YamiShopBindException(YamiHttpStatus.UNAUTHORIZED,"token error"); } // 防止解密后的token是脚本,从而对redis进行攻击,uuid只能是数字和小写字母 if (!PrincipalUtil.isSimpleChar(decryptToken)) { throw new YamiShopBindException(YamiHttpStatus.UNAUTHORIZED,"token error"); } return decryptToken; } public String getAccessKey(String accessToken) { return OauthCacheNames.ACCESS + accessToken; } public String getUserIdToAccessKey(String approvalKey) { return OauthCacheNames.UID_TO_ACCESS + approvalKey; } public String getRefreshToAccessKey(String refreshToken) { return OauthCacheNames.REFRESH_TO_ACCESS + refreshToken; } public TokenInfoVO storeAndGetVo(UserInfoInTokenBO userInfoInToken) { TokenInfoBO tokenInfoBO = storeAccessToken(userInfoInToken); TokenInfoVO tokenInfoVO = new TokenInfoVO(); tokenInfoVO.setAccessToken(tokenInfoBO.getAccessToken()); tokenInfoVO.setRefreshToken(tokenInfoBO.getRefreshToken()); tokenInfoVO.setExpiresIn(tokenInfoBO.getExpiresIn()); return tokenInfoVO; } public void deleteCurrentToken(String accessToken) { String decryptToken = decryptToken(accessToken); UserInfoInTokenBO userInfoInToken = getUserInfoByAccessToken(accessToken, true); String uidKey = getUserIdToAccessKey(getApprovalKey(userInfoInToken.getSysType().toString(), userInfoInToken.getUserId())); Long size = redisTemplate.opsForSet().size(uidKey); if (size == null || size == 0) { return; } // List<String> tokenInfoBoList = stringRedisTemplate.opsForSet().pop(uidKey, size); List<String> tokenInfoBoList = new ArrayList<>(); for (int i = 0; i < size; i++) { String pop = stringRedisTemplate.opsForSet().pop(uidKey); tokenInfoBoList.add(pop); } if (CollUtil.isEmpty(tokenInfoBoList)) { return; } String dbAccessToken = null; String dbRefreshToken = null; List<byte[]> list = new ArrayList<>(); for (String accessTokenWithRefreshToken : tokenInfoBoList) { String[] accessTokenWithRefreshTokenArr = accessTokenWithRefreshToken.split(StrUtil.COLON); dbAccessToken = accessTokenWithRefreshTokenArr[0]; if (decryptToken.equals(dbAccessToken)) { dbRefreshToken = accessTokenWithRefreshTokenArr[1]; redisTemplate.delete(getRefreshToAccessKey(dbRefreshToken)); redisTemplate.delete(getAccessKey(dbAccessToken)); continue; } list.add(accessTokenWithRefreshToken.getBytes(StandardCharsets.UTF_8)); } if (CollUtil.isNotEmpty(list)) { redisTemplate.executePipelined((RedisCallback<Object>) connection -> { connection.sAdd(uidKey.getBytes(StandardCharsets.UTF_8), ArrayUtil.toArray(list, byte[].class)); return null; }); } } }

2.保存在token信息里面的用户信息

/**
 * 保存在token信息里面的用户信息
 *
 * @author 菠萝凤梨
 * @date 2022/3/25 17:33
 */
@Data
public class UserInfoInTokenBO {

    /**
     * 用户在自己系统的用户id
     */
    private String userId;

    /**
     * 租户id (商家id)
     */
    private Long shopId;

    /**
     * 昵称
     */
    private String nickName;

    /**
     * 系统类型
     * @see com.yami.shop.security.common.enums.SysTypeEnum
     */
    private Integer sysType;

    /**
     * 是否是管理员
     */
    private Integer isAdmin;

    private String bizUserId;

    /**
     * 权限列表
     */
    private Set<String> perms;

    /**
     * 状态 1 正常 0 无效
     */
    private Boolean enabled;

    /**
     * 其他Id
     */
    private Long otherId;

}

3.在系统上下文保存用户信息类AuthUserContext

import com.alibaba.ttl.TransmittableThreadLocal;
import com.yami.shop.security.common.bo.UserInfoInTokenBO;

/**
 * @author FrozenWatermelon
 * @date 2020/7/16
 */
public class AuthUserContext {

    private static final ThreadLocal<UserInfoInTokenBO> USER_INFO_IN_TOKEN_HOLDER = new TransmittableThreadLocal<>();

    public static UserInfoInTokenBO get() {
        return USER_INFO_IN_TOKEN_HOLDER.get();
    }

    public static void set(UserInfoInTokenBO userInfoInTokenBo) {
        USER_INFO_IN_TOKEN_HOLDER.set(userInfoInTokenBo);
    }

    public static void clean() {
        if (USER_INFO_IN_TOKEN_HOLDER.get() != null) {
            USER_INFO_IN_TOKEN_HOLDER.remove();
        }
    }
}

4.授权过滤器AuthFilter

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.handler.HttpHandler;
import com.yami.shop.security.common.adapter.AuthConfigAdapter;
import com.yami.shop.security.common.bo.UserInfoInTokenBO;
import com.yami.shop.security.common.manager.TokenStore;
import com.yami.shop.security.common.util.AuthUserContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
 * 授权过滤,只要实现AuthConfigAdapter接口,添加对应路径即可:
 *
 * @author 菠萝凤梨
 * @date 2022/3/25 17:33
 */
@Component
public class AuthFilter implements Filter {

    private static final Logger logger = LoggerFactory.getLogger(AuthFilter.class);

    @Autowired
    private AuthConfigAdapter authConfigAdapter;

    @Autowired
    private HttpHandler httpHandler;

    @Autowired
    private TokenStore tokenStore;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        String requestUri = req.getRequestURI();

        List<String> excludePathPatterns = authConfigAdapter.excludePathPatterns();

        AntPathMatcher pathMatcher = new AntPathMatcher();
        // 如果匹配不需要授权的路径,就不需要校验是否需要授权
        if (CollectionUtil.isNotEmpty(excludePathPatterns)) {
            for (String excludePathPattern : excludePathPatterns) {
                if (pathMatcher.match(excludePathPattern, requestUri)) {
                    chain.doFilter(req, resp);
                    return;
                }
            }
        }

        String accessToken = req.getHeader("Authorization");
        // 也许需要登录,不登陆也能用的uri
        boolean mayAuth = pathMatcher.match(AuthConfigAdapter.MAYBE_AUTH_URI, requestUri);


        UserInfoInTokenBO userInfoInToken = null;

        try {
            // 如果有token,就要获取token
            if (StrUtil.isNotBlank(accessToken)) {
                userInfoInToken = tokenStore.getUserInfoByAccessToken(accessToken, true);
            }
            else if (!mayAuth) {
                // 返回前端401
                httpHandler.printServerResponseToWeb(HttpStatus.UNAUTHORIZED.getReasonPhrase(), HttpStatus.UNAUTHORIZED.value());
                return;
            }
            // 保存上下文
            AuthUserContext.set(userInfoInToken);

            chain.doFilter(req, resp);

        }catch (Exception e) {
            // 手动捕获下非controller异常
            if (e instanceof YamiShopBindException) {
                httpHandler.printServerResponseToWeb(e.getMessage(), ((YamiShopBindException) e).getHttpStatusCode());
            } else {
                throw e;
            }
        } finally {
            AuthUserContext.clean();
        }
    }
}

5.注册授权过滤器的配置

import cn.hutool.core.util.ArrayUtil;
import com.yami.shop.security.common.adapter.AuthConfigAdapter;
import com.yami.shop.security.common.adapter.DefaultAuthConfigAdapter;
import com.yami.shop.security.common.filter.AuthFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;

import javax.servlet.DispatcherType;

/**
 * 授权配置
 *
 * @author 菠萝凤梨
 * @date 2022/3/25 17:33
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class AuthConfig {

    @Autowired
    private AuthFilter authFilter;

    @Bean
    @ConditionalOnMissingBean
    public AuthConfigAdapter authConfigAdapter() {
        return new DefaultAuthConfigAdapter();
    }


    @Bean
    @Lazy
    public FilterRegistrationBean<AuthFilter> filterRegistration(AuthConfigAdapter authConfigAdapter) {
        FilterRegistrationBean<AuthFilter> registration = new FilterRegistrationBean<>();
        // 添加过滤器
        registration.setFilter(authFilter);
        // 设置过滤路径,/*所有路径
        registration.addUrlPatterns(ArrayUtil.toArray(authConfigAdapter.pathPatterns(), String.class));
        registration.setName("authFilter");
        // 设置优先级
        registration.setOrder(0);
        registration.setDispatcherTypes(DispatcherType.REQUEST);
        return registration;
    }

}

6.定义需要拦截和放行路径的配置接口(需自定义接口继承复写方法,实现自定义)

/**
 * 实现该接口之后,修改需要授权登陆的路径,不需要授权登陆的路径
 * @author 菠萝凤梨
 * @date 2022/3/25 17:31
 */
public interface AuthConfigAdapter {
    /**
     * 也许需要登录才可用的url
     */
    String MAYBE_AUTH_URI = "/**/ma/**";

    /**
     * 需要授权登陆的路径
     * @return 需要授权登陆的路径列表
     */
    List<String> pathPatterns();

    /**
     * 不需要授权登陆的路径
     * @return 不需要授权登陆的路径列表
     */
    List<String> excludePathPatterns();
}

7.用于在系统中快捷获取用户信息的工具类SecurityUtils

import com.yami.shop.common.util.HttpContextUtils;
import com.yami.shop.security.api.model.YamiUser;
import com.yami.shop.security.common.bo.UserInfoInTokenBO;
import com.yami.shop.security.common.util.AuthUserContext;
import lombok.experimental.UtilityClass;

/**
 * @author LGH
 */
@UtilityClass
public class SecurityUtils {

    private static final String USER_REQUEST = "/p/";

    /**
     * 获取用户
     */
    public YamiUser getUser() {
        if (!HttpContextUtils.getHttpServletRequest().getRequestURI().startsWith(USER_REQUEST)) {
            // 用户相关的请求,应该以/p开头!!!
            throw new RuntimeException("yami.user.request.error");
        }
        UserInfoInTokenBO userInfoInTokenBO = AuthUserContext.get();

        YamiUser yamiUser = new YamiUser();
        yamiUser.setUserId(userInfoInTokenBO.getUserId());
        yamiUser.setBizUserId(userInfoInTokenBO.getBizUserId());
        yamiUser.setEnabled(userInfoInTokenBO.getEnabled());
        yamiUser.setShopId(userInfoInTokenBO.getShopId());
        yamiUser.setStationId(userInfoInTokenBO.getOtherId());
        return yamiUser;
    }
}

8.登录接口代码

@PostMapping("/login")
public ResponseEntity<TokenInfoVO> login(
            @Valid @RequestBody AuthenticationDTO authenticationDTO) {
        String mobileOrUserName = authenticationDTO.getUserName();
        User user = getUser(mobileOrUserName);

        Integer type = authenticationDTO.getType();
        String code = authenticationDTO.getCode();

        String decryptPassword = passwordManager.decryptPassword(authenticationDTO.getPassWord());

        // 半小时内密码输入错误十次,已限制登录30分钟
        passwordCheckManager.checkPassword(SysTypeEnum.ORDINARY, authenticationDTO.getUserName(), decryptPassword, user.getLoginPassword());

        UserInfoInTokenBO userInfoInToken = new UserInfoInTokenBO();
        userInfoInToken.setUserId(user.getUserId());
        userInfoInToken.setSysType(SysTypeEnum.ORDINARY.value());
        userInfoInToken.setEnabled(user.getStatus() == 1);
        // 存储token返回vo
        TokenInfoVO tokenInfoVO = tokenStore.storeAndGetVo(userInfoInToken);
        return ResponseEntity.ok(tokenInfoVO);
    }

 private User getUser(String mobileOrUserName) {
        User user = null;
        // 手机验证码登陆,或传过来的账号很像手机号
        if (PrincipalUtil.isMobile(mobileOrUserName)) {
            user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUserMobile, mobileOrUserName));
        }
        // 如果不是手机验证码登陆, 找不到手机号就找用户名
        if  (user == null) {
//            user = userMapper.selectOneByUserName(mobileOrUserName);
            user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getAccount, mobileOrUserName));
        }
        if (user == null) {
            throw new YamiShopBindException("账号或密码不正确");
        }
        return user;
    }

9.刷新token的代码

public class TokenController {

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private MapperFacade mapperFacade;

    @PostMapping("/token/refresh")
    public ResponseEntity<TokenInfoVO> refreshToken(@Valid @RequestBody RefreshTokenDTO refreshTokenDTO) {
        TokenInfoBO tokenInfoServerResponseEntity = tokenStore
                .refreshToken(refreshTokenDTO.getRefreshToken());
        return ResponseEntity
                .ok(mapperFacade.map(tokenInfoServerResponseEntity, TokenInfoVO.class));
    }

}

10.退出登录的代码

public class LogoutController {

    @Autowired
    private TokenStore tokenStore;

    @PostMapping("/logOut")
    public ResponseEntity<Void> logOut(HttpServletRequest request) {
        String accessToken = request.getHeader("Authorization");
        if (StrUtil.isBlank(accessToken)) {
            return ResponseEntity.ok().build();
        }
        // 删除该用户在该系统当前的token
        tokenStore.deleteCurrentToken(accessToken);
        return ResponseEntity.ok().build();
    }
}

 

posted @ 2022-11-04 19:06  White_白  阅读(1086)  评论(1编辑  收藏  举报