使用gateway的全局过滤器实现单点登录

一、流程图

在这里插入图片描述

二、举例

在springcloud_common中导入JWT依赖
代码如下(示例):

        <!-- JWT -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <scope>runtime</scope>
        </dependency>

.配置yaml

代码如下(示例):secret 自定义

config:
  jwt:
    secret: ashdjakhsdhaslkdhalsjdlasjdlaksjdlkasjdlasjdlkasdjlasjdaslkdjasl1
    expire: 600

JWT的配置类

@Component
public class JWTConfig {

    @Value("${config.jwt.secret}")  //ashdjakhsdhaslkdhalsjdlasjdlaksjdlkasjdlasjdlkasdjlasjdaslkdjasl1
    private String secret;
    @Value("${config.jwt.expire}")  // 600
    private long expire;

    /**
     *  把指定的 UserDTO 对象 生成 token[jwt]
     */
    public  String generateJwt(LoginDTO member){

        // 加密
        byte[] keyBytes = secret.getBytes();
        // 获得密钥对象
        SecretKey key = Keys.hmacShaKeyFor(keyBytes);

        String token = Jwts.builder()
                .setHeaderParam("typ", "JWT") //令牌类型
                //.setHeaderParam("alg", "HS256") //签名算法
                .setIssuedAt(new Date()) //签发时间
                .setExpiration(new Date(System.currentTimeMillis() + expire*1000)) //过期时间
                .claim("id", member.getId())
                .claim("userName", member.getUserName())
                .claim("avatarUrl", member.getAvatarUrl())
                .claim("roleid",member.getRoleid())
                .signWith(key, SignatureAlgorithm.HS256).compact();
        return token;
    }

    /**
     * 解析jwt
     * @param jwtToken
     * @return
     */
    public LoginDTO checkJwt(String jwtToken){

        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(this.secret.getBytes()).parseClaimsJws(jwtToken);
        // map
        Claims claims = claimsJws.getBody();
        Integer id = claims.get("id",Integer.class);
        String userName = claims.get("userName",String.class);
        String avatarUrl = claims.get("avatarUrl",String.class);
        Integer roleid = claims.get("roleid",Integer.class);

        return LoginDTO.builder()
                .id(id)
                .roleid(roleid)
                .avatarUrl(avatarUrl)
                .userName(userName)
                .build();
    }
}

主程序导入JWTconfig结构

在这里插入图片描述
写AdminRoleController层

@RestController
@RequestMapping("/admin/role")
@Api(description = "后台管理-角色接口")
public class AdminRoleController {

    @Autowired
    private RoleServiceImpl roleService;

    /**
     *  根据 登录用户的角色id  去查询 菜单权限
     */
    @GetMapping("/menus")
    // @CheckLogin  // 加上这个注解 这个方法执行之前 要先判断登录状态
    public R getMenuListByRoleid(HttpServletRequest request, @RequestHeader("roleid") Integer roleid){
        // 1 获得当前用户的角色id
        //Object strroleid = request.getAttribute("roleid");
        //Integer roleid = Integer.parseInt(strroleid.toString());
        //System.out.println("当前用户的角色id: "+roleid);
        // 通过角色id  查询当前角色的菜单的权限
        List<MenuDTO> dtos = roleService.findRightByRoleidForMenu(roleid);
        return new R(ResponseEnum.SUCCESS,dtos);
    }

    @GetMapping("/actions/{roleid}")
    public List<String> getActions(@PathVariable("roleid") Integer roleid){
        return roleService.selectActionRightsByRoleid(roleid);
    }
}

RoleServiceImpl层代码

@Service
public class RoleServiceImpl implements RoleServiceI{
    /*
     *根据角色id获得这个角色的权限,收纳并返回
     * */
    @Autowired
    private RoleMapper roleMapper;

    /*
    根据角色id找到对应的权限菜单
    * */
    @Override
    public List<MenuDTO> findRightByRoleidForMenu(Integer Roleid){
        List<MenuDTO> roots = roleMapper.celectRightByRoleidAndParentid(Roleid, 0);
        for (MenuDTO root : roots) {
            Integer rightid = root.getRightid();
            List<MenuDTO> child = roleMapper.celectRightByRoleidAndParentid(Roleid, rightid);
            root.setChildren(child);
        }
        return roots;
    }

    @Override
    @Cacheable(value="selectActionRightsByRoleid",key="#roleid" )
    public List<String> selectActionRightsByRoleid(Integer roleid) {

        List<String> strings = roleMapper.selectActionRightsByRoleid(roleid);
        return strings;
    }

}

提供根据角色id 查询 这个角色拥有的所有能够访问的路径的权限
Mapper

<select id="selectActionRightsByRoleid" parameterType="map" resultType="string">																																										
  select b.righturl from role_action_right a																																										
  left join action_right b																																										
  on a.rightid=b.rightid																																										
  where a.roleid=#{roleid}																																										
  and b.righttype!=0																																										
</select>																																										

配置Vue到网关的跨域配置类

@Component
public class WebCrossOrigin {
    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedMethod("*");
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**", config);
        return new CorsWebFilter(source);
    }
}

在gateway中设置全局过滤器检查是否登录

package com.csqf.gateway.filters;

import com.alibaba.fastjson.JSON;
import com.csqf.common.config.JWTConfig;
import com.csqf.common.result.R;
import com.csqf.common.result.ResponseEnum;
import com.csqf.pojo.DTO.LoginDTO;
import org.springframework.beans.factory.annotation.Autowired;
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.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.function.Consumer;

@Component
public class LoginGlobalFilter implements GlobalFilter, Ordered {

    @Autowired
    private JWTConfig jwtConfig;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 0 获得访问的路径 如果是login 的请求直接 进入service
        String path = exchange.getRequest().getPath().toString();
        if(path.equals("/admin/user/login1")){
            return chain.filter(exchange);
        }
        // 1 获得请求头中的token
        String jwt = exchange.getRequest().getHeaders().getFirst("X-Token");

        // 在过滤器中我们不能获得 下面的方法 上的注解
        //  但是你要求的流进
        // 2 如果没有jwt 以为没有登录
        if(StringUtils.isEmpty(jwt)){
            System.out.println("没有登录");
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.OK);
            // 改变响应的类型
            response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
//            String fastResult = JsonUtils.objectToJson(new R(ResponseEnum.NO_LOGIN));
            String fastResult = JSON.toJSONString(new R(ResponseEnum.HAS_NO_TOKEN,null));
            DataBuffer dataBuffer = response.bufferFactory().allocateBuffer().write(fastResult.getBytes(StandardCharsets.UTF_8));
            return response.writeWith(Mono.just(dataBuffer));

        }

        try {
            LoginDTO loginDTO = jwtConfig.checkJwt(jwt);
            //向headers中放文件,记得build
            //将现在的request 变成 change对象
            Consumer<HttpHeaders> httpHeaders = httpHeader -> {
                httpHeader.set("roleid", loginDTO.getRoleid()+"");
                httpHeader.set("userid", loginDTO.getId()+"");
            };
            ServerHttpRequest serverHttpRequest = exchange.getRequest().mutate().headers(httpHeaders).build();
            exchange.mutate().request(serverHttpRequest).build();

            return chain.filter(exchange);
        } catch (Exception e) {
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.OK);
            // 改变响应的类型
            response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
            String fastResult = JSON.toJSONString(new R(ResponseEnum.TOKEN_TIMEOUT,null));
            DataBuffer dataBuffer = response.bufferFactory().allocateBuffer().write(fastResult.getBytes(StandardCharsets.UTF_8));
            return response.writeWith(Mono.just(dataBuffer));
        }
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

在网关中开启feign客户端
在这里插入图片描述

创建伪客户端

@Service
@FeignClient("right-6003")
public interface RightFeignService {

    /**
     *  根据角色id 查询当前角色的url 集合
     */
    @GetMapping("/admin/role/actions/{roleid}")
    public List<String> getActions(@PathVariable("roleid") Integer roleid);
	}
}

创建全局过滤器【检查当前角色 是否具备权限 访问当前的路径】

@Component
public class ActionCheckGloablFilter implements GlobalFilter, Ordered {
    @Autowired
    private JWTConfig jwtConfig;
    @Autowired
    private RightFeignService rightFeignService;

    // 过滤逻辑
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        // 1 确定哪些动作权限需要验证
        // 1,1 获得访问的路径
        String path = exchange.getRequest().getPath().toString();
        
        // 1.2 判断路径
        if(!path.endsWith("/check")){
            return chain.filter(exchange);
        }

        //1.3 获得当前的roleid
        String jwt = exchange.getRequest().getHeaders().getFirst("X-Token");

        LoginDTO loginDTO = jwtConfig.checkJwt(jwt);
        Integer roleid = loginDTO.getRoleid();

        // 1.4 获得当前角色能够访问的路径的集合
        List<String> paths = rightFeignService.getActions(roleid);

        // 1.5 是否包含 如果不包含抛出异常
        if(!paths.contains(path)){
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.OK);
            // 改变响应的类型
            response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
            String fastResult = JSON.toJSONString(new R(ResponseEnum.HAS_NO_RIGHT,null));

            DataBuffer dataBuffer = response.bufferFactory().allocateBuffer().write(fastResult.getBytes(StandardCharsets.UTF_8));
            return response.writeWith(Mono.just(dataBuffer));

        }

        return chain.filter(exchange);

    }

    // 返回的数字越小 就越先起作用
    @Override
    public int getOrder() {
        return 1;
    }
}

这里报错有用到一个配置类FeignConfig

/**
 * @description: feign配置
 */
@SpringBootConfiguration
public class FeignConfig {
    @Bean
    public Decoder feignDecoder() {
        return new ResponseEntityDecoder(new SpringDecoder(feignHttpMessageConverter()));
    }

    public ObjectFactory<HttpMessageConverters> feignHttpMessageConverter() {
        final HttpMessageConverters httpMessageConverters = new HttpMessageConverters(new GateWayMappingJackson2HttpMessageConverter());
        return new ObjectFactory<HttpMessageConverters>() {
            @Override
            public HttpMessageConverters getObject() throws BeansException {
                return httpMessageConverters;
            }
        };
    }

    public class GateWayMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
        GateWayMappingJackson2HttpMessageConverter() {
            List<MediaType> mediaTypes = new ArrayList<>();
            mediaTypes.add(MediaType.valueOf(MediaType.TEXT_HTML_VALUE + ";charset=UTF-8"));
            setSupportedMediaTypes(mediaTypes);
        }
    }
}
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jsxllht

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值