单点登录的实现流程

什么是单点登录

单点登录(Single Sign On),简称为 SSO,在分布式架构项目中,只需要在一个节点进行登录验证,就能够在其它的所有相关节点实现访问。

实现方案:

  1. JWT+Gateway方案

  2. OAuth2方案

  3. 共享session

这里我们实现一下第一种方案:

 1.用户发送请求给网关,在网关当中设置的有登录白名单,白名单放行

2.放行后去访问用户服务,进行密码校验,如果密码正确进入登录成功处理器,生成token

3.返回token给前台,保存到localstorage中

4.然后再次发送请求并携带token进入网关,在网关对token进行验证,验证成功之后放心,失败返回错误信息给前台

 具体实现:

网关配置:

spring:
  cloud:
    gateway:
      routes: # 路由
        - id: order-service-route
          uri: lb://order-service # 服务名称
          predicates: 
          # 断言
            - Path=/order/**,/orders/** # 匹配路径
        - id: product-service-route
          uri: lb://product-service
          predicates:
            - Path=/product/**,/products/**
        - id: user-service-route
          uri: lb://user-service
          predicates:
            - Path=/user/**,/login/**,/logout/**
      globalcors:
        cors-configurations: 
        # 跨域配置
          '[/**]':
           # 匹配所有路径
            allowed-origins: 
            # 允许的域名
              - "http://localhost:8080"
            allowed-headers: "*" 
            # 允许的请求头
            allowed-methods: "*" 
            # 允许的方法
            allow-credentials: true # 是否携带cookie
user:
  white-list:
   # 自定义白名单
    - /login
    - /logout

白名单配置:

@Data
@Configuration
@ConfigurationProperties(prefix = "user")
public class WhiteListConfig {

    //放行白名单
    private List<String> whiteList;
}

网关过滤器:

@Slf4j
@Component
public class AuthenticationFilter implements GlobalFilter, Ordered {

    @Autowired
    WhiteListConfig whiteListConfig;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//        获得请求响应对象
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
//        对白名单地址放行
        List<String> whiteList = whiteListConfig.getWhiteList();
        for (String str : whiteList){
            if (request.getURI().getPath().contains(str)){
                log.info("白名单放行{}",request.getURI().getPath());
                return chain.filter(exchange);
            }
        }
        //获得请求头中Authorization token信息
        String token = request.getHeaders().getFirst("Authorization");
        try {
            String username = JwtUtil.getUsernameFromToken(token, RsaUtil.publicKey);
            log.info("{}解析成功,放行{}",username,request.getURI().getPath());
            return chain.filter(exchange);
        }catch (Exception e){
            log.error("token解析失败",e);
            DataBuffer wrap = response.bufferFactory().wrap("验证错误,需要登录".getBytes());
            return response.writeWith(Mono.just(wrap));
        }
    }

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

登录成功处理器:

        具体要看前台要返回的是user对象还是username进行适当修改

@Slf4j
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {

    @Resource
    private UserMapper userMapper;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        //获得用户名
        User user = (User) authentication.getPrincipal();
        //将用户名生成jwt token
        String token = JwtUtil.generateToken(user.getUsername(), RsaUtil.privateKey, JwtUtil.EXPIRE_MINUTES);
        //将token 发送给前端
        com.blb.common.entity.User user1 = userMapper.selectOne(new QueryWrapper<com.blb.common.entity.User>().lambda().eq(com.blb.common.entity.User::getUsername, user.getUsername()));
        UserTokenVO userTokenVo = new UserTokenVO(user1,token);
        ResponseResult.write(response, ResponseResult.ok(userTokenVo));
        log.info("user:{}  token:{}",user.getUsername() , token);
    }
}

security配置:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private LoginSuccessHandler loginSuccessHandler;

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //配置自定义登录逻辑
        auth.userDetailsService(userDetailsService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置放行url
        http.authorizeRequests()
                .antMatchers("/swagger-ui.html","/swagger-resources/**","/webjars/**","/*/api-docs"
                ,"/login","/logout").permitAll()
                .anyRequest().authenticated()               //配置其它url要验证
                .and()
                .formLogin()                                //配置登录相关
                .successHandler(loginSuccessHandler)  //配置登录成功的处理器
                .failureHandler((req,resp,auth) -> {        //配置登录失败的处理器
                    ResponseResult.write(resp, ResponseResult.error(ResponseStatus.LOGIN_ERROR));
                })
                .and()
                .exceptionHandling()
                .authenticationEntryPoint((req,resp,auth) ->{ //配置拦截未登录请求的处理
                    ResponseResult.write(resp, ResponseResult.error(ResponseStatus.AUTHENTICATE_ERROR));
                })
                .and()
                .logout()
                .logoutSuccessHandler((req,resp,auth) ->{     //配置登出处理器
                    ResponseResult.write(resp, ResponseResult.ok("注销成功"));
                })
                .clearAuthentication(true)                     //清除验证缓存
                .and()
                .csrf()
                .disable()                                    //关闭csrf保护
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS); //不使用session

    }
}

user的vo对象

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserTokenVO {

    private User user;
    private String token;
}

在serviceImpl进行用户名验证:

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        User user = userMapper.selectOne(new QueryWrapper<User>().lambda().eq(User::getUsername, s));
        if (user == null){
            throw new UsernameNotFoundException("用户名不存在");
        }
        return new org.springframework.security.core.userdetails.User(s,user.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList(""));
    }
}