单点登录的实现流程

什么是单点登录

单点登录(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(""));
    }
}

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JWT(JSON Web Token)单点登录是一种基于 token 的验证机制,能够实现多个应用之间的用户认证与授权状态共享,从而实现单点登录实现 JWT 单点登录的基本流程如下: 1. 用户通过认证中心登录,认证中心颁发一个 JWT Token 给用户。 2. 用户访问其他应用,将 JWT Token 通过请求头或参数方式传递给应用。 3. 应用接收到 JWT Token 后进行验证,验证通过后即可获取用户信息,实现单点登录。 下面是使用 .NET 实现 JWT 单点登录的示例代码: 1. 安装 JWT 库 使用 NuGet 包管理器安装 System.IdentityModel.Tokens.Jwt 库。 2. 创建 JWT Token 在认证中心中创建 JWT Token 并返回给用户。示例代码如下: ```csharp public string GenerateToken(User user) { var secretKey = Encoding.UTF8.GetBytes(_configuration["Jwt:SecretKey"]); var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(secretKey), SecurityAlgorithms.HmacSha256); var claims = new List<Claim> { new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Name, user.Name), new Claim(ClaimTypes.Email, user.Email), new Claim("Role", user.Role) }; var tokenOptions = new JwtSecurityToken( issuer: _configuration["Jwt:Issuer"], audience: _configuration["Jwt:Audience"], claims: claims, expires: DateTime.Now.AddMinutes(Convert.ToDouble(_configuration["Jwt:ExpirationMinutes"])), signingCredentials: signingCredentials ); return new JwtSecurityTokenHandler().WriteToken(tokenOptions); } ``` 3. 验证 JWT Token 在其他应用中接收 JWT Token 并进行验证。示例代码如下: ```csharp public bool ValidateToken(string token) { var secretKey = Encoding.UTF8.GetBytes(_configuration["Jwt:SecretKey"]); try { new JwtSecurityTokenHandler().ValidateToken(token, new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(secretKey), ValidateIssuer = true, ValidIssuer = _configuration["Jwt:Issuer"], ValidateAudience = true, ValidAudience = _configuration["Jwt:Audience"], ValidateLifetime = true, ClockSkew = TimeSpan.Zero }, out SecurityToken validatedToken); return true; } catch { return false; } } ``` 以上代码仅为示例,实际使用中需要根据具体情况进行修改。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值