• SpringSecurityOAtu2+JWT实现微服务版本的单点登录


    何为单点登录

    单点登录通俗的话来讲在微服务当中,在一个服务登录后就能免去另一个服务的登录操作,所谓单点登录.
    就好像你在微博总网站里登录后,然后在微博里面的某一个模块点进去后,就发现这个模块竟然不用登录了,不是因为这个模块与主网站是一体的用一个SpringSecurity就可以搞定了,这里面的水深着呢!
    感兴趣更深这个SpringSecurity建议去看看图灵课堂的SpringSecurity,建议不要看尚硅谷的,那个版本说实话感觉有点老式了,教你手写,其实我觉得,你既然学到了这个层次了,就应该能建立起快速打通一个新技术的能力,这个"打通"的意思不是要你深入,而是会用!别想着深入,因为没有什么实际意义,现在不是我们小白应该做的事,我们小白只要打好基础就可以了,比如java,jvm,spring,mysql这些!
    没有SpringSecurity基础就别看这篇文章了,你可能看得懂,但是你肯定实现不出来,不信我你就看

    认证中心

    新建一个微服务模块,可以另外建项目,也可以直接在你微服务项目里建立模块就可以了.

    maven配置

      <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-oauth2</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-security</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    用户登录逻辑

    @Component
    public class SheepUserDetailsService implements UserDetailsService {
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Override
        public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    
            if( !"admin".equals(s) )
                throw new UsernameNotFoundException("用户" + s + "不存在" );
    
            return new User( s, passwordEncoder.encode("123456"), AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_NORMAL,ROLE_MEDIUM"));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    OAtuh2配置

    配置服务中心

    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
        @Autowired
        AuthenticationManager authenticationManager;
    
        @Autowired
        SheepUserDetailsService sheepUserDetailsService;
    
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    
            // 定义了两个客户端应用的通行证
            clients.inMemory()
                    .withClient("admin")
                    .secret(new BCryptPasswordEncoder().encode("123456"))
                    .authorizedGrantTypes("authorization_code", "refresh_token","password")
                    .scopes("all")
                    .autoApprove(true)
                    .redirectUris("http://192.168.216.1:8001/login","http://192.168.216.1:8004/login");
    
        }
    
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    
            endpoints.tokenStore(jwtTokenStore()).accessTokenConverter(jwtAccessTokenConverter());
            DefaultTokenServices tokenServices = (DefaultTokenServices) endpoints.getDefaultAuthorizationServerTokenServices();
            tokenServices.setTokenStore(endpoints.getTokenStore());
            tokenServices.setSupportRefreshToken(true);
            tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
            tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
            tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1)); // 一天有效期
            endpoints.tokenServices(tokenServices);
    
            //密码模式配置
            endpoints.authenticationManager(authenticationManager).userDetailsService(sheepUserDetailsService);
        }
    
        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
            security
                    .tokenKeyAccess("isAuthenticated()")
                    .checkTokenAccess("permitAll()")
                    .allowFormAuthenticationForClients();
        }
    
        @Bean
        public TokenStore jwtTokenStore() {
            return new JwtTokenStore(jwtAccessTokenConverter());
        }
    
        @Bean
        public JwtAccessTokenConverter jwtAccessTokenConverter(){
            JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
            converter.setSigningKey("testKey");
            return converter;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    配置规则中心

    Configuration
    public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
    
        @Override
        @Bean
        public AuthenticationManager authenticationManager() throws Exception {
            return super.authenticationManager();
        }
    
        @Autowired
        private UserDetailsService userDetailsService;
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
    
        @Autowired
        private CustomLogoutSuccessHandler customLogoutSuccessHandler;
    
    
        @Bean
        public DaoAuthenticationProvider authenticationProvider() {
            DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
            authenticationProvider.setUserDetailsService(userDetailsService);
            authenticationProvider.setPasswordEncoder(passwordEncoder());
            authenticationProvider.setHideUserNotFoundExceptions(false);
            return authenticationProvider;
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.requestMatchers().antMatchers("/oauth/**","/login/**","/logout/**","/uac/oauth/token","/remove")
                    .and()
                    .authorizeRequests()
                    .antMatchers("/oauth/**").authenticated()
                    .and()
                    .formLogin().permitAll()
                    .and()
                    .logout()
                    .logoutSuccessHandler(customLogoutSuccessHandler)
                    // 无效会话
                    .invalidateHttpSession(true)
                    // 清除身份验证
                    .clearAuthentication(true)
                    .permitAll();
        }
    
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.authenticationProvider(authenticationProvider());
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    @Component
    public class CustomLogoutSuccessHandler extends AbstractAuthenticationTargetUrlRequestHandler implements LogoutSuccessHandler {
        @Override
        public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            // 将子系统的cookie删掉
            //建议将token也删除,直接写个controller接口就可以了,可以在前端调用/logout的同时调用删除token接口
            Cookie[] cookies = request.getCookies();
            if(cookies != null && cookies.length>0){
                for (Cookie cookie : cookies){
                    cookie.setMaxAge(0);
                    cookie.setPath("/");
                    response.addCookie(cookie);
                }
            }
            super.handle(request, response, authentication);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    请求模块

    下面这个是请求模块,也就是独立出一个微服务,假如这个微服务是做业务的,会给认证中心发出请求,然后去热证,这个模块也算登录了.
    那么有道友会问:其他模块在该模块登录后还要登录吗?答案:不用!
    至于要以什么为基础,接着往下看:

    请求模块这个依赖很关键

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-oauth2</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-security</artifactId>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这个yml也很重要

    auth-server: http://localhost:8085/uac
    security:
      oauth2:
        client:
          client-id: admin
          client-secret: 123456
          user-authorization-uri: ${auth-server}/oauth/authorize #认证
          access-token-uri: ${auth-server}/oauth/token #获取token
        resource:
          jwt:
            key-uri: ${auth-server}/oauth/token_key #忘了,反正要写上
         
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    上面的认证和获取token这两个配置很重要,还有一个/oauth/check_token,这个是用来检查token是否合法的,这些都怎么用,是什么,后面会说

    下面这个也很重要

    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    @EnableOAuth2Sso
    public class ClientWebsecurityConfigurer extends WebSecurityConfigurerAdapter {
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
        //下面表示该模块所有请求都要拦截
        //因为在yml配置了认证中心的各个路径,所以会自动跳转到认证中心去认证
        //如果你想在当前模块排除哪些拦截,可以在下面去排除
            http.antMatcher("/**").authorizeRequests()
                    .anyRequest().authenticated();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    真实请求
    下面就可以正式去请求了,这里很多网友都会有疑问,就是前面我在认证中心授予了权限之后,在其他模块该如何去权限的规定呢?其实很简单,有很多博主都没有说,直接加上注解就可以了.

        @GetMapping("/get")
        @PreAuthorize("hasAuthority('ROLE_NORMAL')")
        public String get(HttpServletRequest request){
            System.out.println("函数进来了");
            return "uusb1j";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这个时候,如果该微服务的get请求过来,就会跳转到认证中心去认证.

    一些小问题

    1. 这个时候只要有相同配置的模块都不用自行登录了.如果有一个模块进行登出请求,所有服务都会进行登出.
    2. 注意认证中心有项配置redirectUris(“http://192.168.216.1:8001/login”,“http://192.168.216.1:8004/login”),这个配置代表认证成功后重定向去哪个地址,但并不会真的重定向去对应模块的login页,而是重定向去你请求前被拦截的地址,但是这个有讲究就是必须配置请求前所在模块的地址,每个模块对应一个重定向地址,最好是重定向到对应模块的/login页,其他页也行.如果你从网关过来,然后你这里重定向回网关是不行的,除非你网关有相关操作,不然你网关只是一个转发功能,是先转发到对应模块,然后发现该模块的某个地址不可访问,才去认证中心请求权限的,所以这里的重定向地址还是具体到对应模块才对,其他不行,有多个用","隔开就行.
  • 相关阅读:
    如何发布一个属于自己的 npm 包
    全相联映射、直接映射、组相连映射
    谷歌翻译用不了的解决方案
    count(1)、count(*) 与 count(列) 的区别?
    OSPF 动态路由协议(思科、华为)
    mysql-MVCC
    C++中list详解
    系统架构设计师(第二版)学习笔记----信息安全系统及信息安全技术
    Oauth2系列8:何谓JWT令牌?
    “高级Vue状态管理 - Vuex的魅力与应用“
  • 原文地址:https://blog.csdn.net/weixin_47303191/article/details/124916250