单点登录实现之基于OAuth2.0协议

OAuth2

  • 概念

    OAuth(Open Authorization,开放授权)是为用户资源的授权定义了一个安全、开放及简单的标准,第三方无需知道用户的账号及密码,就可获取到用户的授权信息。
    OAuth2.0是OAuth协议的延续版本,但不向后兼容OAuth 1.0即完全废止了OAuth1.0

  • 应用场景

    第三方应用授权登录:在APP或者网页接入一些第三方应用时,时常会需要用户登录另一个合作平台,比如QQ,微博,微信的授权登录,第三方应用通过oauth2方式获取用户信息

开发流程

  • 微信开发文档流程说明

    1. 第三方发起微信授权登录请求,微信用户允许授权第三方应用后,微信会拉起应用或重定向到第三方网站,并且带上授权临时票据code参数;

    2. 通过code参数加上AppID和AppSecret等,通过API换取access_token;

    3. 通过access_token进行接口调用,获取用户基本数据资源或帮助用户实现基本操作。

  • 授权登录流程图
    OAuth2用户授权登录流程图

OAuth2实现单点登录

基本介绍

  • 实现原理

    在OAuth2在有授权服务器、资源服务器、客户端这样几个角色,当我们用它来实现SSO的时候是不需要资源服务器这个角色的,有授权服务器和客户端就够了。

    授权服务器当然是用来做认证的,客户端就是各个应用系统,我们只需要登录成功后拿到用户信息以及用户所拥有的权限即可

  • 注意事项

    之前我一直认为把那些需要权限控制的资源放到资源服务器里保护起来就可以实现权限控制,其实是我想错了,权限控制还得通过Spring Security或者自定义拦截器来做

  • 概念介绍

    SSO:是一种思想,或者说是一种解决方案,是抽象的,我们要做的就是按照它的这种思想去实现它

    OAuth2:是用来允许用户授权第三方应用访问他在另一个服务器上的资源的一种协议,它不是用来做单点登录的,但我们可以利用它来实现单点登录。在本例实现SSO的过程中,受保护的资源就是用户的信息(包括,用户的基本信息,以及用户所具有的权限),而我们想要访问这这一资源就需要用户登录并授权,OAuth2服务端负责令牌的发放等操作

    JWT:令牌的生成我们采用JWT,也就是说JWT是用来承载用户的Access_Token的

    Spring Security:是用于安全访问的,这里我们我们用来做访问权限控制

相关配置

认证服务器配置

依赖配置
  • pom.xml

    
        <?xml version="1.0" encoding="UTF-8"?>
        <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
            <modelVersion>4.0.0</modelVersion>
            <parent>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>2.1.3.RELEASE</version>
                <relativePath/> <!-- lookup parent from repository -->
            </parent>
            <groupId>com.cjs.sso</groupId>
            <artifactId>oauth2-sso-auth-server</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <name>oauth2-sso-auth-server</name>
    
            <properties>
                <java.version>1.8</java.version>
            </properties>
    
            <dependencies>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-data-jpa</artifactId>
                </dependency>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-data-redis</artifactId>
                </dependency>
                <!-- spring-cloud-starter-oauth2包含了 spring-cloud-starter-security,不用再单独引入 -->
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-security</artifactId>
                </dependency>
                <!-- 重要依赖 -->
                <dependency>
                    <groupId>org.springframework.security.oauth.boot</groupId>
                    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
                    <version>2.1.3.RELEASE</version>
                </dependency>
                <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.session</groupId>
                    <artifactId>spring-session-data-redis</artifactId>
                </dependency>
                <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <scope>runtime</scope>
                </dependency>
                <dependency>
                    <groupId>org.projectlombok</groupId>
                    <artifactId>lombok</artifactId>
                    <optional>true</optional>
                </dependency>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-test</artifactId>
                    <scope>test</scope>
                </dependency>
                <dependency>
                    <groupId>org.springframework.security</groupId>
                    <artifactId>spring-security-test</artifactId>
                    <scope>test</scope>
                </dependency>
    
                <dependency>
                    <groupId>org.apache.commons</groupId>
                    <artifactId>commons-lang3</artifactId>
                    <version>3.8.1</version>
                </dependency>
                <dependency>
                    <groupId>com.alibaba</groupId>
                    <artifactId>fastjson</artifactId>
                    <version>1.2.56</version>
                </dependency>
    
            </dependencies>
    
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                    </plugin>
                </plugins>
            </build>
    
        </project>
    
    
    
AuthorizationServerConfig(自定义认证配置类)
介绍
  • 参考链接

    https://blog.csdn.net/qq_33460562/article/details/79351938

  • AuthorizationServerConfigurerAdapter类

    AuthorizationServerConfigurerAdapter类用于配置授权的相关信息,属于OAuth2 配置文件,是配置的核心,主要配置客户端,配置token存储方式

  • @EnableAuthorizationServer注解

    
        @Target({ElementType.TYPE})
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        @Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class})
        public @interface EnableAuthorizationServer {
    
        }
    
    
  • configure(AuthorizationServerSecurityConfigurer oauthServer)方法

    
        //用来配置令牌端点的安全约束
        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
            
            oauthServer.allowFormAuthenticationForClients();//允许客户端访问 OAuth2 授权接口,否则请求 token 会返回 401。
            oauthServer.checkTokenAccess("isAuthenticated()");//允许已授权用户访问 checkToken 接口
            oauthServer.tokenKeyAccess("isAuthenticated()");//允许已授权用户访问获取 token 接口
        }
    
    
    
    
  • configure(ClientDetailsServiceConfigurer clients)方法

    1. 内存中配置客户端详情信息
    
    
        //用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
           
            //使用存在内存中配置两个客户端应用的通行证,相当于硬编码了,正式环境下的做法是持久化到数据库中,比如 mysql 中
            clients.inMemory()
                    .withClient("sheep1")
                    .secret(new BCryptPasswordEncoder().encode("123456"))
                    .authorizedGrantTypes("authorization_code", "refresh_token")
                    .redirectUris("http://localhost:8086/login")
                    .scopes("all")
                    .autoApprove(false)
                    .and()
                    .withClient("sheep2")
                    .secret(new BCryptPasswordEncoder().encode("123456"))
                    .authorizedGrantTypes("authorization_code", "refresh_token")
                    .redirectUris("http://localhost:8087/login")
                    .scopes("all")
                    .autoApprove(false);
        }
    
    
    
    1. 官方数据库结构中配置客户端详情信息
    
    
        @Autowired
        private DataSource dataSource;
    
        //用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    
            clients.jdbc(dataSource);
            
        }
    
        //数据库表结构见下面
    
    1. 自定义数据库结构中配置客户端详情信息
    
    
        //用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化,可以通过自定义数据库来存储调取详情信息
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    
            // 配置客户端应用通信证, 用于client认证
            clients.withClientDetails(getClientDetails());
          
        }
    
        @Bean   // 声明ApplyClientDetailService
        public ApplyClientDetailService getClientDetails() {
            return new ApplyClientDetailService();
        }
    
    
    
    

    ApplyClientDetailService自定义获取客户端详情类

    
        import org.apache.tomcat.jdbc.pool.DataSource;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.security.oauth2.provider.ClientDetails;
        import org.springframework.security.oauth2.provider.ClientDetailsService;
        import org.springframework.security.oauth2.provider.ClientRegistrationException;
        import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
        import oauth.security.client.service.ApplyService;
    
        /**
         *
         * 1. 在配置客户端中,使用了ApplyClientDetailService类,是自定义的获取Client的一个类,实现ClientDetailsService接口,
         * 重写loadClientByClientId方法
         *
         * 2. 对Client的访问主要依靠JdbcClientDetailsService类的实现,必须使用官方给出的数据库结构,
         * 如果想自定义数据库结构,可以根据需求重写ClientDetailsService类的实现
         **/
        public class ApplyClientDetailService implements ClientDetailsService {
    
            @Autowired
            private ApplyService applyService;
    
            @Autowired
            private DataSource dataSource;
    
            @Override
            public ClientDetails loadClientByClientId(String applyName) throws ClientRegistrationException {
    
                /*
                // 使用mybatic验证client是否存在 ,根据需求写sql
                Map clientMap = applyService.findApplyById(applyName);
    
                if(clientMap == null) {
                    throw new ClientRegistrationException("应用" + applyName + "不存在!");
                }*/
    
        //        MyJdbcClientDetailsService jdbcClientDetailsService= new MyJdbcClientDetailsService(dataSource, "authentication");
                JdbcClientDetailsService jdbcClientDetailsService= new JdbcClientDetailsService(dataSource);
                ClientDetails clientDetails = jdbcClientDetailsService.loadClientByClientId(applyName);
    
                return clientDetails;
            }
        }
    
    

    ClientDetailsServiceConfigurer参数的重写,在这里定义各个端的约束条件:

    1. ClientId、Client-Secret:这两个参数对应请求端定义的 cleint-id 和 client-secret

    2. authorizedGrantTypes 可以包括如下几种设置中的一种或多种:

      authorization_code:授权码类型。
      implicit:隐式授权类型。
      password:资源所有者(即用户)密码类型。
      client_credentials:客户端凭据(客户端ID以及Key)类型。
      refresh_token:通过以上授权获得的刷新令牌来获取新的令牌。
      accessTokenValiditySeconds:token 的有效期

    3. scopes:用来限制客户端访问的权限,在换取的 token 的时候会带上 scope 参数,只有在 scopes 定义内的,才可以正常换取 token。

  • configure(AuthorizationServerEndpointsConfigurer endpoints)方法

    1. 数据库保存token
    
    
        @Autowired
        AuthenticationManager authenticationManager;
    
        @Autowired
        private DataSource dataSource;
    
        // 初始化JdbcTokenStore
        @Autowired
        public TokenStore getTokenStore() {
            return new JdbcTokenStore(dataSource);
        }
    
    
        //用来配置令牌(token)的访问端点和令牌服务(token services)
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            
            endpoints
                    .tokenStore(getTokenStore())   // 数据库保存token
                    .authenticationManager(authenticationManager); //调用此方法才能支持 password 模式。
        }
    
    
    
    
    1. Redis保存token
    
        
        @Autowired
        AuthenticationManager authenticationManager;
    
        @Autowired
        RedisConnectionFactory redisConnectionFactory;
    
        //用来配置令牌(token)的访问端点和令牌服务(token services)
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints
                    .tokenStore(new RedisTokenStore(redisConnectionFactory))// 指定 token 的存储方式,redis保存token
                    .authenticationManager(authenticationManager);    //调用此方法才能支持 password 模式。
    
        }
    
    
    
    
    1. 普通JWT保存token
    
        
        @Autowired
        AuthenticationManager authenticationManager;
    
        @Autowired
        public UserDetailsService nbspUserDetailsService;
    
    
        //用来配置令牌(token)的访问端点和令牌服务(token services)
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    
            endpoints
                .tokenStore(jwtTokenStore())  // 指定 token 的存储方式,普通jwt方式存储
                .accessTokenConverter(jwtAccessTokenConverter())
                .userDetailsService(nbspUserDetailsService)//设置用户验证服务,自定义用户实现类实现 UserDetailsService接口,重写loadUserByUsername方法,返回用户信息对象
                .authenticationManager(authenticationManager);//调用此方法才能支持 password 模式
    
        }
    
        @Bean
        public TokenStore jwtTokenStore() {
            return new JwtTokenStore(jwtAccessTokenConverter());
        }
    
        @Bean
        public JwtAccessTokenConverter jwtAccessTokenConverter() {
            JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
            accessTokenConverter.setSigningKey("dev");
            return accessTokenConverter;
        }
    
    
    
    
    1. 增强JWT保存token
    
    
        // 由于JWT配置已经抽取至JwtTokenConfig配置类,项目启动后预先加载,这里可以直接注入
        @Autowired
        private TokenStore jwtTokenStore;
    
        @Autowired//默认按类型注入,若存在多个实例,需要结合@Qualifier注解指定注入Bean的名称
        private JwtAccessTokenConverter jwtAccessTokenConverter;
    
        @Autowired
        private TokenEnhancer jwtTokenEnhancer;
        
        @Autowired
        AuthenticationManager authenticationManager;
    
        @Autowired
        public UserDetailsService nbspUserDetailsService;
    
    
        //用来配置令牌(token)的访问端点和令牌服务(token services)
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    
            TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
            List<TokenEnhancer> enhancerList = new ArrayList<>();
            enhancerList.add(jwtTokenEnhancer);
            enhancerList.add(jwtAccessTokenConverter);
            enhancerChain.setTokenEnhancers(enhancerList);
            endpoints.tokenStore(jwtTokenStore)// 指定 token 的存储方式,增强jwt方式存储
                    .accessTokenConverter(jwtAccessTokenConverter)
                    .tokenEnhancer(enhancerChain)
                    .userDetailsService(nbspUserDetailsService)//设置用户验证服务,自定义用户实现类实现 UserDetailsService接口,重写loadUserByUsername方法,返回用户信息对象
                    .authenticationManager(authenticationManager);//调用此方法才能支持 password 模式
    
        }
    
    
    

    JwtTokenConfig配置类

    
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.security.oauth2.provider.token.TokenEnhancer;
        import org.springframework.security.oauth2.provider.token.TokenStore;
        import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
        import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
    
        //自动注入JWT配置
        @Configuration
        public class JwtTokenConfig {
    
            @Bean//@Bean就放在方法上,就是让方法去产生一个Bean,然后交给Spring容器
            public TokenStore jwtTokenStore() {
                return new JwtTokenStore(jwtAccessTokenConverter());
            }
    
            @Bean
            public JwtAccessTokenConverter jwtAccessTokenConverter() {
                JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
                accessTokenConverter.setSigningKey("dev");//  Sets the JWT signing key
                return accessTokenConverter;
            }
    
            @Bean
            public TokenEnhancer jwtTokenEnhancer(){
            return new JWTokenEnhancer();
            }
        }
    
    
    
    
    

    JWTokenEnhancer增强配置类

    
    
        import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
        import org.springframework.security.oauth2.common.OAuth2AccessToken;
        import org.springframework.security.oauth2.provider.OAuth2Authentication;
        import org.springframework.security.oauth2.provider.token.TokenEnhancer;
    
        import java.util.HashMap;
        import java.util.Map;
    
        /**
        * 如果想在 JWT 中加入额外的字段(比方说用户的其他信息),
        * 需要自定义增强实现类,实现 spring security oauth2提供的 TokenEnhancer 增强器接口
        * 重写enhance方法(RedisToken 的方式同样可以)
        * 
        */
        public class JWTokenEnhancer implements TokenEnhancer {
    
            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
                Map<String, Object> info = new HashMap<>();
                info.put("jwt-ext", "JWT 扩展信息");
                ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(info);
                return oAuth2AccessToken;
            }
        }
    
    
    
  • 常见自定义重写实现类

    1. 重写UserDetailsService接口实现类
    
        import com.alibaba.fastjson.JSON;
        import com.cjs.sso.domain.MyUser;
        import com.cjs.sso.entity.SysPermission;
        import com.cjs.sso.entity.SysUser;
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.security.core.authority.SimpleGrantedAuthority;
        import org.springframework.security.core.userdetails.UserDetails;
        import org.springframework.security.core.userdetails.UserDetailsService;
        import org.springframework.security.core.userdetails.UsernameNotFoundException;
        import org.springframework.security.crypto.password.PasswordEncoder;
        import org.springframework.stereotype.Service;
        import org.springframework.util.CollectionUtils;
    
        import java.util.ArrayList;
        import java.util.List;
    
        /**
        * 
        * 一般需要重写,自定义用户详情实现类,需要实现UserDetailsService接口,重写loadUserByUsername方法,
        * 通过用户名查找用户信息,并返回用户详情信息,包括用户名,加密后的密码,和权限集合
        *
        */
        @Slf4j
        @Service
        public class NbspUserDetailsService implements UserDetailsService {
    
            @Autowired
            private PasswordEncoder passwordEncoder;
    
            @Autowired
            private UserService userService;
    
            @Autowired
            private PermissionService permissionService;
    
            @Override
            public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
                SysUser sysUser = userService.getByUsername(username);
                if (null == sysUser) {
                    log.warn("用户{}不存在", username);
                    throw new UsernameNotFoundException(username);
                }
                List<SysPermission> permissionList = permissionService.findByUserId(sysUser.getId());
                List<SimpleGrantedAuthority> authorityList = new ArrayList<>();
                if (!CollectionUtils.isEmpty(permissionList)) {
                    for (SysPermission sysPermission : permissionList) {
                        authorityList.add(new SimpleGrantedAuthority(sysPermission.getCode()));
                    }
                }
    
                MyUser myUser = new MyUser(sysUser.getUsername(), passwordEncoder.encode(sysUser.getPassword()), authorityList);
    
                log.info("登录成功!用户: {}", JSON.toJSONString(myUser));
    
                return myUser;
    
                //返回默认用户详情信息
                //return new org.springframework.security.core.userdetails.User(username,password, authorities);
    
            }
        }
    
    
    
    
    1. 重写User类
    
        import lombok.Data;
        import org.springframework.security.core.GrantedAuthority;
        import org.springframework.security.core.userdetails.User;
    
        import java.util.Collection;
    
        /**
        * 大部分时候直接用User即可不必扩展
        * 
        */
        @Data
        public class MyUser extends User {
    
            private Integer departmentId;   //  举个例子,部门ID
    
            private String mobile;  //  举个例子,假设我们想增加一个字段,这里我们增加一个mobile表示手机号
    
            public MyUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
                super(username, password, authorities);
            }
    
            public MyUser(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
                super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
            }
        }
    
    
案例代码


     import org.springframework.beans.factory.annotation.Autowired;
     import org.springframework.context.annotation.Bean;
     import org.springframework.context.annotation.Configuration;
     import org.springframework.context.annotation.Primary;
     import org.springframework.security.core.token.DefaultToken;
     import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
     import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
     import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
     import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
     import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
     import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
     import org.springframework.security.oauth2.provider.token.TokenStore;
     import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
     import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

     import javax.sql.DataSource;

     /**
     * @author CharlesYan
     * @date 2020-04-17
     */
     @Configuration
     @EnableAuthorizationServer
     public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

         @Autowired
         private DataSource dataSource;

         @Override
         public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
             security.allowFormAuthenticationForClients();
             security.tokenKeyAccess("isAuthenticated()");
         }

         @Override
         public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
             //普通数据方式获取用户详情信息
             clients.jdbc(dataSource);
         }

         @Override
         public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
             //普通jwt方式保存token
             endpoints.accessTokenConverter(jwtAccessTokenConverter());
             endpoints.tokenStore(jwtTokenStore());
     //        endpoints.tokenServices(defaultTokenServices());
         }

         /*@Primary
         @Bean
         public DefaultTokenServices defaultTokenServices() {
             DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
             defaultTokenServices.setTokenStore(jwtTokenStore());
             defaultTokenServices.setSupportRefreshToken(true);
             return defaultTokenServices;
         }*/

         @Bean
         public JwtTokenStore jwtTokenStore() {
             return new JwtTokenStore(jwtAccessTokenConverter());
         }

         @Bean
         public JwtAccessTokenConverter jwtAccessTokenConverter() {
             JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
             jwtAccessTokenConverter.setSigningKey("cjs");   //  Sets the JWT signing key
             return jwtAccessTokenConverter;
         }

     }



WebSecurityConfig(自定义安全配置类)
介绍
  • 参考链接

    https://blog.csdn.net/sinat_29899265/article/details/80736498

  • WebSecurityConfigurerAdapter类

    WebSecurityConfigurerAdapter类是个适配器,属于spring security 基础配置,在配置的时候,需要我们自己写个配置类去继承它,然后编写自己所特殊需要的配置

  • @EnableWebSecurity注解

    
        //@EnableWebSecurity完成的工作便是加载了WebSecurityConfiguration,AuthenticationConfiguration这两个核心配置类,也就此将spring security的职责划分为了配置安全信息,配置认证信息两部分
    
        @Retention(RetentionPolicy.RUNTIME)
        @Target({ElementType.TYPE})
        @Documented
        @Import({WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class})
        @EnableGlobalAuthentication
        @Configuration
        public @interface EnableWebSecurity {
            boolean debug() default false;
        }
    
    
    
  • configure(HttpSecurity http)方法

    
        @Configuration
        @EnableWebSecurity
        public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    
            //1. 配置Security的认证策略(定义授权规则), 每个模块配置使用and结尾
            @Override
            protected void configure(HttpSecurity http) throws Exception {
                http
                    .authorizeRequests()//2. authorizeRequests()配置路径拦截,表明路径访问所对应的权限,角色,认证信息。
                        .antMatchers("/resources/**", "/signup", "/about").permitAll()
                        .antMatchers("/admin/**").hasRole("ADMIN")
                        .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
                        .anyRequest().authenticated()
                        .and()
                    .formLogin()//3. formLogin()对应表单认证相关的配置
                        .usernameParameter("username")
                        .passwordParameter("password")
                        .failureForwardUrl("/login?error")
                        .loginPage("/login")
                        .permitAll()
                        .and()
                    .logout()//4. logout()对应了注销相关的配置
                        .logoutUrl("/logout")
                        .logoutSuccessUrl("/index")
                        .permitAll()
                        .and()
                    .httpBasic()//5. httpBasic()可以配置basic登录
                        .disable();
            }
        }
    
    
  • configure(AuthenticationManagerBuilder auth)方法

    1. 在内存中配置用户认证信息
    
    
        @Configuration
        @EnableWebSecurity
        public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
            //1. 配置的是认证信息(定义认证规则)
            //作用:AuthenticationManagerBuilder 这个类就是AuthenticationManager的建造者, 我们只需要向这个类中, 配置用户信息, 就能生成对应的AuthenticationManager, 这个类是用户身份的管理者, 是认证的入口, 因此,我们需要通过这个配置,向security提供真实的用户身份。
            @Override
            protected void configure(AuthenticationManagerBuilder auth) throws Exception {
                auth
                    .inMemoryAuthentication()//2. 设置从缓存中获取已事先定义好的认证信息(配置一个内存中的用户认证器)
                    .withUser("admin").password("admin").roles("USER");
            } 
    
           
        }
    
    
    
    1. 数据库中获取认证信息
    
    
        @Configuration
        @EnableWebSecurity
        public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
            @Autowired
            private NbspUserDetailsService userDetailsService;
    
    
            @Bean
            public PasswordEncoder passwordEncoder() {
    
                return new BCryptPasswordEncoder();
            }
    
            //1. 配置的是认证信息(定义认证规则)
            //作用:AuthenticationManagerBuilder 这个类就是AuthenticationManager的建造者, 我们只需要向这个类中, 配置用户信息, 就能生成对应的AuthenticationManager, 这个类是用户身份的管理者, 是认证的入口, 因此,我们需要通过这个配置,向security提供真实的用户身份。
            @Override
            protected void configure(AuthenticationManagerBuilder auth) throws Exception {
                auth
                    .userDetailsService(userDetailsService)//3. userDetailsService是用户自定义用户详情服务类, 这个类的作用就是去获取用户信息,比如从数据库中获取。 这样的话,AuthenticationManager在认证用户身份信息的时候,就会从中获取用户身份,和从http中拿的用户身份做对比
                    .passwordEncoder(passwordEncoder());
            }
    
            //注意事项:UserDetailService会默认加载DaoAuthenticationProvider
        }
    
    
    
  • configure(WebSecurity web)方法

    
        @Configuration
        @EnableWebSecurity
        public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
            //全局请求忽略规则配置(比如说静态文件,比如说注册页面)
            @Override
            public void configure(WebSecurity web) throws Exception {
                web
                    .ignoring()//一般只需要重写ignoring()配置
                    .antMatchers("/static/**");
            }
        }
    
    
    
    
案例代码


     import com.cjs.sso.service.NbspUserDetailsService;
     import org.springframework.beans.factory.annotation.Autowired;
     import org.springframework.context.annotation.Bean;
     import org.springframework.context.annotation.Configuration;
     import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
     import org.springframework.security.config.annotation.web.builders.HttpSecurity;
     import org.springframework.security.config.annotation.web.builders.WebSecurity;
     import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
     import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
     import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
     import org.springframework.security.crypto.password.PasswordEncoder;

     /**
     * @author CharlesYan
     * @date 2019-02-11
     */
     @Configuration
     @EnableWebSecurity
     public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

         @Autowired
         private NbspUserDetailsService userDetailsService;

         @Bean
         public PasswordEncoder passwordEncoder() {
             return new BCryptPasswordEncoder();
         }

         @Override
         protected void configure(AuthenticationManagerBuilder auth) throws Exception {
             auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
         }

         @Override
         public void configure(WebSecurity web) throws Exception {
             web.ignoring().antMatchers("/assets/**", "/css/**", "/images/**");
         }

         @Override
         protected void configure(HttpSecurity http) throws Exception {
             http.formLogin()
                     .loginPage("/login")
                     .and()
                     .authorizeRequests()
                     .antMatchers("/login").permitAll()
                     .anyRequest()
                     .authenticated()
                     .and().csrf().disable().cors();
         }

     }




客户端详细信息表(官方)
  • 建表语句

    
    
        create table oauth_client_details (
            client_id VARCHAR(256) PRIMARY KEY,
            resource_ids VARCHAR(256),
            client_secret VARCHAR(256),
            scope VARCHAR(256),
            authorized_grant_types VARCHAR(256),
            web_server_redirect_uri VARCHAR(256),
            authorities VARCHAR(256),
            access_token_validity INTEGER,
            refresh_token_validity INTEGER,
            additional_information VARCHAR(4096),
            autoapprove VARCHAR(256)
        )ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
        INSERT INTO oauth_client_details
            (client_id, client_secret, scope, authorized_grant_types,
            web_server_redirect_uri, authorities, access_token_validity,
            refresh_token_validity, additional_information, autoapprove)
        VALUES
            ('user-client', '$2a$10$o2l5kA7z.Caekp72h5kU7uqdTDrlamLq.57M1F6ulJln9tRtOJufq', 'all',
            'authorization_code,refresh_token,password', null, null, 3600, 36000, null, true);
    
        INSERT INTO oauth_client_details
            (client_id, client_secret, scope, authorized_grant_types,
            web_server_redirect_uri, authorities, access_token_validity,
            refresh_token_validity, additional_information, autoapprove)
        VALUES
            ('order-client', '$2a$10$GoIOhjqFKVyrabUNcie8d.ADX.qZSxpYbO6YK4L2gsNzlCIxEUDlW', 'all',
            'authorization_code,refresh_token,password', null, null, 3600, 36000, null, true);
    
    
    
    
    
  • 注意事项

    client_secret 字段不能直接是 secret 的原始值,需要经过加密。因为是用的 BCryptPasswordEncoder,所以最终插入的值应该是经过 BCryptPasswordEncoder.encode()之后的值

核心配置文件
  • application.yml

    
        spring:
            datasource:
                url: jdbc:mysql://localhost:3306/nbsp?characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
                username: root
                password: 1q2w3e4r
            #    driver-class-name: com.mysql.jdbc.Driver
            jpa:
                show-sql: true
            session:
                store-type: redis
            redis:
                host: 192.168.159.133
                password: redispassword
                port: 6379
        server:
            port: 8080
    
    
    
  • 注意事项

    1. Spring Boot 2.0 之后默认使用 hikari 作为数据库连接池。如果使用其他连接池需要引入相关包,然后对应的增加配置。

客户端配置

依赖配置
  • pom.xml

    
        <?xml version="1.0" encoding="UTF-8"?>
        <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
            <modelVersion>4.0.0</modelVersion>
            <parent>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>2.1.3.RELEASE</version>
                <relativePath/> <!-- lookup parent from repository -->
            </parent>
            <groupId>com.cjs.sso</groupId>
            <artifactId>oauth2-sso-client-member</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <name>oauth2-sso-client-member</name>
            <description>Demo project for Spring Boot</description>
    
            <properties>
                <java.version>1.8</java.version>
            </properties>
    
            <dependencies>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-data-jpa</artifactId>
                </dependency>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-oauth2-client</artifactId>
                </dependency>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-security</artifactId>
                </dependency>
                <dependency>
                    <groupId>org.springframework.security.oauth.boot</groupId>
                    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
                    <version>2.1.3.RELEASE</version>
                </dependency>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-thymeleaf</artifactId>
                </dependency>
                <dependency>
                    <groupId>org.thymeleaf.extras</groupId>
                    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
                    <version>3.0.4.RELEASE</version>
                </dependency>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </dependency>
    
                <dependency>
                    <groupId>com.h2database</groupId>
                    <artifactId>h2</artifactId>
                    <scope>runtime</scope>
                </dependency>
                <dependency>
                    <groupId>org.projectlombok</groupId>
                    <artifactId>lombok</artifactId>
                    <optional>true</optional>
                </dependency>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-test</artifactId>
                    <scope>test</scope>
                </dependency>
                <dependency>
                    <groupId>org.springframework.security</groupId>
                    <artifactId>spring-security-test</artifactId>
                    <scope>test</scope>
                </dependency>
            </dependencies>
    
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                    </plugin>
                </plugins>
            </build>
    
        </project>
    
    
    
WebSecurityConfig(自定义安全配置类)
ResourceServerConfig(资源配置类)
  • Redis保存Token

    
    
        @Configuration
        @EnableResourceServer
        @EnableGlobalMethodSecurity(prePostEnabled = true)
        public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    
            @Value("${security.oauth2.client.client-id}")
            private String clientId;
    
            @Value("${security.oauth2.client.client-secret}")
            private String secret;
    
            @Value("${security.oauth2.authorization.check-token-access}")
            private String checkTokenEndpointUrl;
    
            @Autowired
            private RedisConnectionFactory redisConnectionFactory;
    
            @Bean
            public TokenStore redisTokenStore (){
                return new RedisTokenStore(redisConnectionFactory);
            }
    
            @Bean
            public RemoteTokenServices tokenService() {
                RemoteTokenServices tokenService = new RemoteTokenServices();
                tokenService.setClientId(clientId);
                tokenService.setClientSecret(secret);
                tokenService.setCheckTokenEndpointUrl(checkTokenEndpointUrl);
                return tokenService;
            }
    
            @Override
            public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
                resources.tokenServices(tokenService());
            }
        }
    
    
    
    
  • JWT保存Token

    
    
        @Configuration
        @EnableResourceServer
        @EnableGlobalMethodSecurity(prePostEnabled = true)
        public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
            @Bean
            public TokenStore jwtTokenStore() {
                return new JwtTokenStore(jwtAccessTokenConverter());
            }
    
            @Bean
            public JwtAccessTokenConverter jwtAccessTokenConverter() {
                JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
    
                accessTokenConverter.setSigningKey("dev");
                accessTokenConverter.setVerifierKey("dev");
                return accessTokenConverter;
            }
    
            @Autowired
            private TokenStore jwtTokenStore;
    
            @Override
            public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
                resources.tokenStore(jwtTokenStore);
            }
        }
    
    
    
介绍
  • 相关注解

    在 OAuth2 的概念里,所有的接口都被称为资源,接口的权限也就是资源的权限,所以 Spring Security OAuth2 中提供了关于资源的注解 @EnableResourceServer 和 @EnableWebSecurity的作用类似

注意事项
  • @EnableOAuth2Sso注解
  1. 引入依赖

     <!-- Spring Boot 2.x 中没有@EnableOAuth2Sso这个注解 -->
     <dependency>
         <groupId>org.springframework.security.oauth.boot</groupId>
         <artifactId>spring-security-oauth2-autoconfigure</artifactId>
         <version>2.1.3.RELEASE</version>
     </dependency>



  1. 源码
    EnableOAuth2Sso注解

  2. 简介

    对于OAuth 2.0客户端配置,简化的配置用@EnableOAuth2Client。这个注解做两件事情:

    (1) 创建一个过滤器(ID是oauth2ClientContextFilter)来存储当前的请求和上下文。在请求期间需要进行身份认证时,它管理重定向URI。

    (2) 在请求范围内创建一个AccessTokenRequest类型的bean。对于授权代码(或隐式)授予客户端是很有用的,可以避免与单个用户相关的状态发生冲突。

  3. 参考链接

    https://projects.spring.io/spring-security-oauth/docs/oauth2.html

    https://www.cnblogs.com/trust-freedom/p/12002089.html

    https://www.jianshu.com/p/fe1194ca8ecd

  • 其他

    1. antMatcher()

      是HttpSecurity的一个方法,他只告诉了Spring我只配置了一个我这个Adapter能处理哪个的url,它与authorizeRequests()没有任何关系。

    2. authorizeRequests().antMatchers()

      是在antMatchers()中指定的一个或多个路径,比如执行permitAll()或hasRole()。他们在第一个http.antMatcher()匹配时就会生效。

    所以,WebSecurityConfigurerAdapter与ResourceServerConfigurerAdapter同时使用,其实和spring security的多个HttpSecurity配置是一样的,原理也差不多是一样的。

案例代码
  • ClientWebsecurityConfigurer类

    
        import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
        import org.springframework.security.config.annotation.web.builders.HttpSecurity;
        import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
        import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    
        @Configuration
        @EnableWebSecurity
        @EnableGlobalMethodSecurity(prePostEnabled = true)
        @EnableOAuth2Sso
        public class ClientWebsecurityConfigurer extends WebSecurityConfigurerAdapter {
    
            @Override
            public void configure(HttpSecurity http) throws Exception {
                http.antMatcher("/**").authorizeRequests()
                        .anyRequest().authenticated();
            }
        }
    
    
    
    

参考链接

  • OAuth2实现单点登录SSO

    https://www.cnblogs.com/cjsblog/p/10548022.html

  • Spring Cloud OAuth2 实现用户认证及单点登录

    https://www.cnblogs.com/fengzheng/p/11724625.html

  • OAuth2介绍与使用
    https://www.jianshu.com/p/4f5fcddb4106

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
单点登录(Single Sign-On,简称SSO)是一种身份验证和授权机制,允许用户使用一组凭据登录到多个应用程序和系统,而不需要在每个应用程序中单独输入用户名和密码。 Shiro是一个开源的Java安全框架,提供身份验证、授权、加密、会话管理等功能。Spring Security OAuth 2.0是Spring Security的一个扩展,用于实现OAuth 2.0协议,提供基于令牌的身份验证和授权。 集成Shiro和Spring Security OAuth 2.0可以实现单点登录功能。首先,需要配置一个认证中心作为身份提供者,其他应用程序和系统将依赖该认证中心进行身份验证和用户信息获取。在集成过程中,需要在认证中心和其他应用程序中配置Shiro的过滤器链,并将相关的OAuth 2.0配置添加到Spring Security的配置中。 当用户在一个应用程序中登录时,该应用程序将重定向至认证中心,用户在认证中心完成身份验证后,会生成一个访问令牌(Access Token)。该访问令牌可以在其他应用程序之间传递,并由Shiro验证器进行验证。在其他应用程序中,用户使用访问令牌向认证中心进行验证,通过后就可以无需重新输入用户名和密码,直接访问其他应用程序。 集成Shiro和Spring Security OAuth 2.0实现单点登录的好处是可以减少用户登录的次数,提高用户体验,同时也可以提高系统的安全性,减少密码泄露的风险。这样的集成方案可以适用于多个系统和应用程序之间的跨域身份验证和授权需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值