文章目录
- 一.什么是单点登录?
 - 二.Oauth2整合网关实现微服务单点登录
 - 三.时序图
 - 四.代码实现思路
 - 1.基于OAuth2独立一个认证中心服务出来
 - 2.网关微服务
 - 3产品微服务
 - 4.订单微服务
 - 5.开始测试单点登录
 
一.什么是单点登录?
单点登录(Single Sign On),简称为 SSO,是比较流行的企业业务整合的解决方案之一。它的用途在于,不管多么复杂的应用群,只要在用户权限范围内,那么就可以做到用户只需要登录一次就可以访问权限范围内的所有应用子系统。
二.Oauth2整合网关实现微服务单点登录
网关整合 OAuth2.0 有两种思路:
- 一种是授权服务器生成令牌, 所有请求统一在网关层验证,判断权限等操作;
 - 另一种是由各资源服务处理,网关只做请求转发。
 
比较常用的是第一种,把API网关作为OAuth2.0的资源服务器角色,实现接入客户端权限拦截、令牌解析并转发当前登录用户信息给微服务,这样下游微服务就不需要关心令牌格式解析以及OAuth2.0相关机制了。
网关在认证授权体系里主要负责两件事:
 (1)作为OAuth2.0的资源服务器角色,实现接入方权限拦截。
 (2)令牌解析并转发当前登录用户信息(明文token)给微服务
微服务拿到明文token(明文token中包含登录用户的身份和权限信息)后也需要做两件事:
 (1)用户授权拦截(看当前用户是否有权访问该资源)
 (2)将用户信息存储进当前线程上下文(有利于后续业务逻辑随时获取当前用户信息)
三.时序图

四.代码实现思路
1.基于OAuth2独立一个认证中心服务出来
- 启动OAuth2认证授权中心:需要定义配置类重新AuthorizationServerConfigurerAdapter认证服务配置适配器类的3个configure方法。
以及需要用到spring-security的WebSecurityConfigurerAdapter类等等,这里不贴代码了,只讲思路。 - 比如我认证中心服务配置完,端口启动为:8888。
 
2.网关微服务
我们要自定义认证过滤器。认证过滤器里面需要做:
- 获取请求头里面的token信息
 - 然后传入参数token信息通过rpc调用认证服务 http://auth-server/oauth/check_token进行token认证。
也可以定义授权过滤器:进行token信息里面的自定义权限校验等等,需要使用@Order(1)注解,值越小优先级越高,指定这个过滤器在认证过滤器之后。 - 然后向下游业务系统传递解析后的token信息。
 
我启动端口为:8880,下面是认证过滤器核心代码:
/**
 * 认证过滤器
 */
@Component
@Order(0)
public class AuthenticationFilter implements GlobalFilter, InitializingBean {
    @Autowired
    private RestTemplate restTemplate;
    private static Set<String> shouldSkipUrl = new LinkedHashSet<>();
    @Override
    public void afterPropertiesSet() throws Exception {
        // 不拦截认证的请求
        shouldSkipUrl.add("/oauth/token");
        shouldSkipUrl.add("/oauth/check_token");
        shouldSkipUrl.add("/user/getCurrentUser");
    }
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String requestPath = exchange.getRequest().getURI().getPath();
        
        //不需要认证的url
        if(shouldSkip(requestPath)) {
            return chain.filter(exchange);
        }
        //获取请求头
        String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");
        //请求头为空
        if(StringUtils.isEmpty(authHeader)) {
            throw new RuntimeException("请求头为空");
        }
        TokenInfo tokenInfo=null;
        try {
            //获取token信息
            tokenInfo = getTokenInfo(authHeader);
        }catch (Exception e) {
            throw new RuntimeException("校验令牌异常");
        }
        // tokenInfo
        exchange.getAttributes().put("tokenInfo",tokenInfo);
        return chain.filter(exchange);
    }
    private boolean shouldSkip(String reqPath) {
        for(String skipPath:shouldSkipUrl) {
            if(reqPath.contains(skipPath)) {
                return true;
            }
        }
        return false;
    }
    private TokenInfo getTokenInfo(String authHeader) {
        // 往授权服务发请求 /oauth/check_token
        // 获取token的值
        String token = StringUtils.substringAfter(authHeader, "bearer ");
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        //必须 basicAuth clienId clientSecret
        headers.setBasicAuth(MDA.clientId, MDA.clientSecret);
        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("token", token);
        HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(params, headers);
        ResponseEntity<TokenInfo> response = restTemplate.exchange(MDA.checkTokenUrl, HttpMethod.POST, entity, TokenInfo.class);
        return response.getBody();
    }
}
 
常量类:
public class MDA {
    public static final String clientId = "gateway-server";
    public static final String clientSecret = "123123";
    public static final String checkTokenUrl = "http://auth-server/oauth/check_token";
}
 
3产品微服务
我启动端口为:8084
4.订单微服务
我启动端口为:8082
5.开始测试单点登录
- 比如哪个单点登录接口处理完业务相关逻辑等,然后请求我们的授权服务-通过密码模式获取到access_token

 - 使用同一access_token,请求网关获取订单信息

3.使用同一access_token,请求网关获取商品信息

这样就完成我们的单点登录啦。 












![[vulnhub] Prime 1](https://i-blog.csdnimg.cn/direct/b8e8cc8f549847fb9d9b18af89423380.png)






