创建token

在oj-common-security中引入依赖

创建jwt⼯具类:

package com.bite.common.security.utils;

import com.bite.common.core.comtains.JwtContains;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.HashMap;
import java.util.Map;

public class JwtUtils {

    /**
     * 生成令牌
     *
     * @param claims 数据
     * @param secret 密钥
     * @return 令牌
     */
    public static String createToken(Map<String, Object> claims, String secret) {
        String token = Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
        return token;
    }

    /**
     * 从令牌中获取数据
     *
     * @param token 令牌
     * @param secret 密钥
     * @return 数据
     */
    public static Claims parseToken(String token, String secret) {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
    }
}

createToken

----根据所传的claims和secret创建一个token

注意:这里的secret需要保密随机,不能硬编码,可以定期更换

(所以可以采用nacos来配置secret,这个后面细讲)

硬编码(Hard Coding)是指在程序代码中直接嵌入固定数值、字符串或逻辑,而非通过变量、配置文件或外部输入来动态获取的编程方式。这种做法会导致代码灵活性降低,维护成本增加。以下是关于硬编码的详细解析:

-

里面的这个SignatureAlgorithm.HS512是创建所使用的算法

使用例子:

首先创建一个token,这里由于是实例,所以密钥随便即可

  public static void main(String[] args) {
        Map<String,Object> claim=new HashMap<>();
        claim.put("userid",123456);
        System.out.println(createToken(claim,"ddwdwdwdw5dfw5"));
   }

获取到到token后通过JwtUtil的parseToken进行解码(需要token和secret)


    public static void main(String[] args) {
        String token="eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyaWQiOjEyMzQ1Nn0.F0jIBBpUgyhUPZSSrKVHOBBatVhFEIRSWh6LRjPXC5kdJ3TsFOzByhBryx_nV9nLlFpeE-4ZvOESQMWKn9nDjw";
        Claims claims=parseToken("eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyaWQiOjEyMzQ1Nn0.F0jIBBpUgyhUPZSSrKVHOBBatVhFEIRSWh6LRjPXC5kdJ3TsFOzByhBryx_nV9nLlFpeE-4ZvOESQMWKn9nDjw","ddwdwdwdw5dfw5");
        System.out.println(claims);
    }

目的

在OJ-modules中引入oj-common-security

创建常量类


public class CacheConstants {
    public final static String LOGIN_TOKEN_KEY = "login_tokens:";
    public final static long EXPIRATION = 720;
}
public class JwtContains {
    public static final String LOGIN_USER_ID="userId";
    public static final String LOGIN_USER_KEY="userKey";
}

定义登录用户

@Data
public class LoginUser {
    private Integer identity;
    //普通用户1 管理员2
}

引入依赖

       <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool-all.version}</version>
        </dependency>
        <dependency>
            <groupId>com.bite</groupId>
            <artifactId>oj-common-redis</artifactId>
            <version>${oj-common-redis.version}</version>
            <scope>compile</scope>
        </dependency>

定义TokenService类

@Service
public class TokenService {

    @Autowired
    private RedisService redisService;

    /**
     * 创建令牌
     */
    public String createToken(Long userId,String secret,Integer identity) {
        String userKey = UUID.fastUUID().toString();
        // Jwt存储信息
        Map<String, Object> claim = new HashMap<String, Object>();
        claim.put(JwtContains.LOGIN_USER_ID,userId);
        claim.put(JwtContains.LOGIN_USER_KEY,userKey);
        String token=JwtUtils.createToken(claim,secret);
        String key=CacheConstants.LOGIN_TOKEN_KEY+userKey;
        LoginUser loginUser=new LoginUser();
        loginUser.setIdentity(UserIdentity.ADMIN.getCode());
        redisService.setCacheObject(key, loginUser, CacheConstants.EXPIRATION, TimeUnit.MINUTES);
        return token;
    }
}

核心代码详解:

首先我们通过UUID来获取一个唯一的userKey

创建一个claim,存放着用户的userId以及刚刚创建的userKey;

这个是调用方传入通过nacos得到的密钥,我们需要传给createToken

我们通过claim,secret来生成一个token.

然后我们创建一个变量key(其实就是一个常量+userKey);

然后将key存入redis中,value是用户身份

对login接口中Service部分进行修改

对oj-common-security项目进行修改

身份认证

引入依赖

填入白名单

创建一个白名单类,从nacos中去获取哪些是白名单中的url

@Configuration//配置类
@RefreshScope//实时刷新,使nacos上面的改变立刻生效
@ConfigurationProperties(prefix = "security.ignore")
//
public class IgnoreWhiteProperties {
        /**
         * 放⾏⽩名单配置,⽹关不校验此处的⽩名单
         */
        private List<String> whites = new ArrayList<>();

        public List<String> getWhites() {
                //拿到nacos配置里面的白名单
                return whites;
        }

        public void setWhites(List<String> whites) {
                this.whites = whites;
        }
}

修改nacos

自定义过滤器

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.bite.common.core.comtains.CacheConstants;
import com.bite.common.core.comtains.HttpConstants;
import com.bite.common.core.domain.R;
import com.bite.common.core.enums.ResultCode;
import com.bite.common.core.enums.UserIdentity;
import com.bite.common.redis.service.RedisService;
import com.bite.common.security.model.LoginUser;
import com.bite.common.security.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;

/**
 * ⽹关鉴权
 *
 */
@Slf4j
@Component
public class AuthFilter implements GlobalFilter, Ordered {
    //实现GlobalFilter相当于实现了一个自定义过滤器
    //实现Order是为什么以后有多个过滤器的时候进行先后排序
    // 排除过滤的 uri ⽩名单地址,在nacos⾃⾏添加
    @Autowired
    private IgnoreWhiteProperties ignoreWhite;

    @Value("${jwt.secret}")
    private String secret;

    @Autowired
    private RedisService redisService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        //获取请求
        String url = request.getURI().getPath();
        // 跳过不需要验证的路径
        if (matches(url, ignoreWhite.getWhites())) {
            return chain.filter(exchange);
            //如果是白名单里面的,则跳过身份认证
        }
        //从http请求头中获取token
        String token = getToken(request);
        //判断token是否为空,如果为空说明以前未登录或者登录超市;
        if (StrUtil.isEmpty(token)) {
            return unauthorizedResponse(exchange, "令牌不能为空");
        }
        Claims claims;
        try {
            claims = JwtUtils.parseToken(token, secret); //获取令牌中信息 解析payload中信息
            if (claims == null) {
                return unauthorizedResponse(exchange, "令牌已过期或验证不正确!");
            }
        } catch (Exception e) {
            return unauthorizedResponse(exchange, "令牌已过期或验证不正确!");
        }

        String userKey = JwtUtils.getUserKey(claims); //获取jwt中的key
        boolean isLogin = redisService.hasKey(getTokenKey(userKey));//用得到的key去Redis中查找,如果找不到说明过期了
        if (!isLogin) {
            return unauthorizedResponse(exchange, "登录状态已过期");
        }
        String userid = JwtUtils.getUserId(claims); //判断jwt中的信息是否完整
        if (StrUtil.isEmpty(userid)) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }
        //c端用户只能请求C端功能,B端用户只能请求管理端功能
        LoginUser user = redisService.getCacheObject(getTokenKey(userKey), LoginUser.class);//通过key去Redis里面查找value(存着用户是管理员还是用户)
        if (url.contains(HttpConstants.SYSTEM_URL_PREFIX) && !UserIdentity.ADMIN.getCode().equals(user.getIdentity())) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }
        if (url.contains(HttpConstants.FRIEND_URL_PREFIX) && !UserIdentity.ORDINARY.getCode().equals(user.getIdentity())) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }

        return chain.filter(exchange);
    }

    /**
     * 查找指定url是否匹配指定匹配规则链表中的任意⼀个字符串
     *
     * @param url 指定url
     * @param patternList 需要检查的匹配规则链表
     * @return 是否匹配
     */
    private boolean matches(String url, List<String> patternList) {
        if (StrUtil.isEmpty(url) || CollectionUtils.isEmpty(patternList)) {
            return false;
        }
        for (String pattern : patternList) {
            //从白名单里面分别列出白名单里面的访问地址
            //判断有没有匹配的
            if (isMatch(pattern, url)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断url是否与规则匹配
     * 匹配规则中:
     * ? 表⽰单个字符;
     * * 表⽰⼀层路径内的任意字符串,不可跨层级;
     * ** 表⽰任意层路径;
     *
     * @param pattern 匹配规则
     * @param url 需要匹配的url
     * @return 是否匹配
     */
    private boolean isMatch(String pattern, String url) {
        AntPathMatcher matcher = new AntPathMatcher();
        //将pattern作为匹配规则
        return matcher.match(pattern, url);
    }

    /**
     * 获取缓存key
     */
    private String getTokenKey(String token) {
        return CacheConstants.LOGIN_TOKEN_KEY + token;
    }

    /**
     * 从请求头中获取请求token
     */
    private String getToken(ServerHttpRequest request) {
        String token = request.getHeaders().getFirst(HttpConstants.AUTHENTICATION);
        // 如果前端设置了令牌前缀,则裁剪掉前缀
        if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        return token;
    }

    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) {
        log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
        return webFluxResponseWriter(exchange.getResponse(), msg, ResultCode.FAILED_UNAUTHORIZED.getCode());
    }

    //拼装webflux模型响应
    private Mono<Void> webFluxResponseWriter(ServerHttpResponse response, String msg, int code) {
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        R<?> result = R.fail(code, msg);
        DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());
        return response.writeWith(Mono.just(dataBuffer));
    }

    @Override
    public int getOrder() {
        return -200;
        //定义的值越小越靠前执行
    }
}

不懂的地方看代码注释

这里为什么不抛出异常,然后之前全局异常捕获?

这是因为@RestControllerAdvice只对springweb起作用,而springcloud gataway属于webflux

RedisConfig修改
需要增加@AutoConfigureBefore(RedisAutoConfiguration.class)注解否则会报错如下:

让spring优先注册我们这个RedisTemplate

配置secret

配置redis

然后我们测试一下:

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐