Spring Security parse (five) - Spring Security Oauth2 development

Spring Security parse (five) - Spring Security Oauth2 development

  When learning Spring Cloud, met oauth related content authorization service, always scanty, it was decided to first Spring Security, Spring Security Oauth2 and other rights related to certification content, learning theory and design and organize it again. This series of articles is to strengthen the impression that in the process of learning and understanding written, if infringement, please inform.

Project environment:

  • JDK1.8
  • Spring boot 2.x
  • Spring Security 5.x

  Previous articles have basically the core of the Security speak about it, so from this article we came into contact with Spring Security Oauth2-related content, including Spring Social behind (by its very nature is also based on Oauth2). One thing to note is that we are continuing to develop in the original Spring-Security project, there are some necessary remodeling, but does not affect the function of the previous Security.

A, Oauth2 with Spring Security Oauth2

Oauth2

  Oauth2 have information about the online a lot, but the most recommended or Ruan Yifeng teacher's understanding 2.0 OAuth , I will not repeat here describe Oauth2, but I still need to mention the next important point which:
https://img2018.cnblogs.com/blog/1772687/201909/1772687-20190916135558020-1802087294.jpg

  Picture display mode process is the authorization code process, in which the core is the same as Photo Gallery:

  • The resource owner (Resource Owner): can be understood as user
  • Service Provider (Provider): divided into the authentication server (Authorization server) and server resources (Resource server). After understanding how authentication server resources, it is very simple, such as a mobile phone APP to us by landing QQ, are doing on the authentication server we jump to a QQ landing pages, and authorized the operation, the back of our successful landing we can see the picture and other information, which is after the successful landing to get to the resource server.
  • Third-party applications: You can understand that we are using an APP, the user initiates authorization QQ landing by the APP.

Spring Security Oauth2

   Spring official produced a technical framework to achieve Oauth2 agreement, the latter are in fact a series of articles in the analysis of how it is implemented Oauth2. If you have time, you can look at Spring Security Oauth2 official documents , analysis of my article is to rely on the document.

  Finally, I concluded the difference between these two persons:

  • Oauth2 not a technical framework, but an agreement, it just developed a good standard protocol design, you can use Java to achieve, it can be implemented in any other language.
  • Spring Security Oauth2 is a technology framework that is developed in accordance with Oauth2 agreement.

A, Spring Security Oauth2 development

   In the process of micro-services development in general, the authorization server and the resource server split into two applications, so this project uses this design, but before development, we need to do step by step too important, is the reconstruction project .

First, the reconstruction project

   Why should reconstruct it? Because we are the authority and resources to two servers split up, some of the configuration and functions before development can be shared in two servers, so we can talk about common configuration and functions can be set out separately, and later we developed Spring Security Oauth2 have a number of common configuration (such as Token configuration). Our new security-core sub-module, text messages and other functions previously developed code migration to the sub-module. The final structure to obtain the following items:

https://img2018.cnblogs.com/blog/1772687/201909/1772687-20190916135600056-1080163832.jpg

   After the migration is complete, the original project module replacement module called security-oauth2-authorization, namely the authorization service applications, and references to security-core relies in pom.xml, after migration project structure of the module are as follows:

https://img2018.cnblogs.com/blog/1772687/201909/1772687-20190916135601217-2137618528.jpg

   We can see that internal project only after the migration configuration code and test interfaces, and static html relevant Security.

Second, the authorization server development

A, Maven relies

  In reference to the following dependence pom.xml security-core module:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- 不是starter,手动配置 -->
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <!--请注意下 spring-authorization-oauth2 的版本 务必高于 2.3.2.RELEASE,这是官方的一个bug:
            java.lang.NoSuchMethodError: org.springframework.data.redis.connection.RedisConnection.set([B[B)V
            要求必须大于2.3.5 版本,官方解释:https://github.com/BUG9/spring-security/network/alert/pom.xml/org.springframework.security.oauth:spring-security-oauth2/open
            -->
            <version>2.3.5.RELEASE</version>
        </dependency>

  Add this spring-security-oauth2 dependent, a separately quoted for storage policy token of redis and jwt dependence.

Note the spring-security-oauth2 version must be higher than version 2.3.5, whether the self-storage token use redis strategy will be reported:
org.springframework.data.redis.connection.RedisConnection.set ([B [B) abnormal V

   security-oauth2-authorization module reference pom-Core Security:
`` `

mysql
mysql-connector-java


org.springframework.boot
spring-boot-starter-jdbc



com.zhc
security-core
0.0.1-SNAPSHOT


org.springframework.security.oauth
spring-security-oauth2


    <!-- 不是starter,手动配置 -->
    <dependency>
        <groupId>org.springframework.security.oauth</groupId>
        <artifactId>spring-security-oauth2</artifactId>
        <!--请注意下 spring-authorization-oauth2 的版本 务必高于 2.3.2.RELEASE,这是官方的一个bug:
        java.lang.NoSuchMethodError: org.springframework.data.redis.connection.RedisConnection.set([B[B)V
        要求必须大于2.3.5 版本,官方解释:https://github.com/BUG9/spring-security/network/alert/pom.xml/org.springframework.security.oauth:spring-security-oauth2/open
        -->
        <version>2.3.5.RELEASE</version>
    </dependency>


##### 二、配置授权认证 @EnableAuthorizationServer

&emsp;&emsp;在Spring Security Oauth2 中有一个 **@EnableAuthorizationServer** ,只要我们 在项目中引用到了这个注解,那么一个基本的授权服务就配置好了,但是实际项目中并不这样做。比如要配置redis和jwt 2种存储token策略共存,通过继承 **AuthorizationServerConfigurerAdapter** 来实现。 下列代码是我的一个个性化配置:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

@Resource
private AuthenticationManager authenticationManager;  // 1、引用 authenticationManager 支持 Password 授权模式

private final Map<String, TokenStore> tokenStoreMap; // 2、获取到系统所有的 token存储策略对象 TokenStore ,这里我配置了 redisTokenStore 和 jwtTokenStore

@Autowired(required = false)
private AccessTokenConverter jwtAccessTokenConverter; // 3、 jwt token的增强器

/**
 *  4、由于存储策略时根据配置指定的,当使用redis策略时,tokenEnhancerChain 是没有被注入的,所以这里设置成 required = false
  */
@Autowired(required = false)
private TokenEnhancerChain tokenEnhancerChain; // 5、token的增强器链

@Autowired
private PasswordEncoder passwordEncoder;

@Value("${spring.security.oauth2.storeType}")
private String storeType = "jwt";  // 6、通过获取配置来判断当前使用哪种存储策略,默认jwt

@Autowired
public AuthorizationServerConfiguration(Map<String, TokenStore> tokenStoreMap) {
    this.tokenStoreMap = tokenStoreMap;
}

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    //7、 配置一个客户端,支持客户端模式、密码模式和授权码模式
    clients.inMemory()  // 采用内存方式。也可以采用 数据库方式
            .withClient("client1") // clientId 
            .authorizedGrantTypes("client_credentials", "password", "authorization_code", "refresh_token") // 授权模式
            .scopes("read") // 权限范围 
            .redirectUris("http://localhost:8091/login") // 授权码模式返回code码的回调地址
            // 自动授权,无需人工手动点击 approve
            .autoApprove(true)  
            .secret(passwordEncoder.encode("123456"))
            .and()
            .withClient("client2")
            .authorizedGrantTypes("client_credentials", "password", "authorization_code", "refresh_token")
            .scopes("read")
            .redirectUris("http://localhost:8092/login")
            .autoApprove(true)
            .secret(passwordEncoder.encode("123456"));
}

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    // 设置token存储方式,这里提供redis和jwt
    endpoints
            .tokenStore(tokenStoreMap.get(storeType + "TokenStore"))
            .authenticationManager(authenticationManager);
    if ("jwt".equalsIgnoreCase(storeType)) {
        endpoints.accessTokenConverter(jwtAccessTokenConverter)
                .tokenEnhancer(tokenEnhancerChain);
    }
}

@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
    oauthServer// 开启/oauth/token_key验证端口无权限访问
            .tokenKeyAccess("permitAll()")
            // 开启/oauth/check_token验证端口认证权限访问
            .checkTokenAccess("isAuthenticated()")
            //允许表单认证    请求/oauth/token的,如果配置支持allowFormAuthenticationForClients的,且url中有client_id和client_secret的会走ClientCredentialsTokenEndpointFilter
            .allowFormAuthenticationForClients();
}

}
`
  Configuration where three parts:

  • ClientDetailsServiceConfigurer: Configuring client information. Memory can be used the way, JDBC way, and so, as we can also customize ClientDetailsService like UserDetailsService.
  • AuthorizationServerEndpointsConfigurer: Configuring authorization node information. Here the main configuration tokenStore
  • AuthorizationServerSecurityConfigurer: Security Configuration authority node. Here turn on / oauth / token_key verify that the ports do not have permission to access (calls this interface to obtain key jwt of single-point client starts, so here set to no access to) and / oauth / token configuration support allowFormAuthenticationForClients (url has client_id and client_secret will go ClientCredentialsTokenEndpointFilter)
Third, the configuration TokenStore

  When configuring a certification authority, dependency injection of tokenStore, jwtAccessTokenConverter, tokenEnhancerChain, but these objects are and how to configure the Spring container to inject it? Let us look at the code below:

@Configuration
public class TokenStoreConfig {
   /**
    * redis连接工厂
    */
   @Resource
   private RedisConnectionFactory redisConnectionFactory;

   /**
    * 使用redisTokenStore存储token
    *
    * @return tokenStore
    */
   @Bean
   @ConditionalOnProperty(prefix = "spring.security.oauth2", name = "storeType", havingValue = "redis")
   public TokenStore redisTokenStore() {
       return new RedisTokenStore(redisConnectionFactory);
   }

   @Bean
   PasswordEncoder passwordEncoder(){
       return PasswordEncoderFactories.createDelegatingPasswordEncoder();
   }

   /**
    * jwt的配置
    *
    * 使用jwt时的配置,默认生效
    */
   @Configuration
   @ConditionalOnProperty(prefix = "spring.security.oauth2", name = "storeType", havingValue = "jwt", matchIfMissing = true)
   public static class JwtTokenConfig {

       @Resource
       private SecurityProperties securityProperties;
       /**
        * 使用jwtTokenStore存储token
        * 这里通过 matchIfMissing = true 设置默认使用 jwtTokenStore
        *
        * @return tokenStore
        */
       @Bean
       public TokenStore jwtTokenStore() {
           return new JwtTokenStore(jwtAccessTokenConverter());
       }

       /**
        * 用于生成jwt
        *
        * @return JwtAccessTokenConverter
        */
       @Bean
       public JwtAccessTokenConverter jwtAccessTokenConverter() {
           JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
           //生成签名的key,这里使用对称加密
           accessTokenConverter.setSigningKey(securityProperties.getOauth2().getJwtSigningKey());
           return accessTokenConverter;
       }

       /**
        * 用于扩展JWT
        *
        * @return TokenEnhancer
        */
       @Bean
       @ConditionalOnMissingBean(name = "jwtTokenEnhancer")
       public TokenEnhancer jwtTokenEnhancer() {
           return new JwtTokenEnhance();
       }

       /**
        * 自定义token扩展链
        *
        * @return tokenEnhancerChain
        */
       @Bean
       public TokenEnhancerChain tokenEnhancerChain() {
           TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
           tokenEnhancerChain.setTokenEnhancers(Arrays.asList(new JwtTokenEnhance(), jwtAccessTokenConverter()));
           return tokenEnhancerChain;
       }
   }
}

  Note: This configuration class applies to apply to the authority and resources server, the configuration class is on security-core module

Fourth, the new configuration application.yml
spring:
  redis:
    host: 127.0.0.1
    port: 6379
  security:
    oauth2:
      storeType: redis
      jwt:
        SigningKey: oauth2
Fifth, start the test

1, the authorization code pattern : grant_type = authorization_code

  (1) Access on the browser / oauth / authorize obtain authorization code:
http://localhost:9090/oauth/authorize?response_type=code&client_id=client1&scope=read&state=test&redirect_uri=http://localhost:8091/login
If you are not logged in before, then jump to the login screen (where the account password and login SMS verification code can be), the successful jump to the address we set callback (our client is a single sign-on), we can see the code code from the browser address bar

  (2) Postman request / oauth / token acquired token:
localhost:9090/oauth/token?grant_type=authorization_code&code=i4ge7B&redirect_uri=http://localhost:8091/login

https://img2018.cnblogs.com/blog/1772687/201909/1772687-20190916135603001-634225769.jpg

   Note to fill client information Authorization, following a curl request:

curl -X POST \
 'http://localhost:9090/oauth/token?grant_type=authorization_code&code=Q38nnC&redirect_uri=http://localhost:8091/login' \
 -H 'Accept: */*' \
 -H 'Accept-Encoding: gzip, deflate' \
 -H 'Authorization: Basic Y2xpZW50MToxMjM0NTY=' \
 -H 'Cache-Control: no-cache' \
 -H 'Connection: keep-alive' \
 -H 'Content-Length: ' \
 -H 'Content-Type: application/x-www-form-urlencoded' \
 -H 'Cookie: remember-me=; JSESSIONID=F6F6DE2968113DDE4613091E998D77F4' \
 -H 'Host: localhost:9090' \
 -H 'Postman-Token: f37b9921-4efe-44ad-9884-f14e9bd74bce,3c80ffe3-9e1c-4222-a2e1-9694bff3510a' \
 -H 'User-Agent: PostmanRuntime/7.16.3' \
 -H 'cache-control: no-cache'

   Response message:
{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiI5MDAxIiwic2NvcGUiOlsicmVhZCJdLCJleHAiOjE1Njg2NDY0NzksImF1dGhvcml0aWVzIjpbImFkbWluIl0sImp0aSI6ImY5ZDBhNmZhLTAxOWYtNGU5Ny1iMmI4LWI1OTNlNjBiZjk0NiIsImNsaWVudF9pZCI6ImNsaWVudDEiLCJ1c2VybmFtZSI6IjkwMDEifQ.4BjG_LggZt2RJr0VzXTSmsk71EIUDGvrQsL_OPsg8VA", "token_type": "bearer", "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiI5MDAxIiwic2NvcGUiOlsicmVhZCJdLCJhdGkiOiJmOWQwYTZmYS0wMTlmLTRlOTctYjJiOC1iNTkzZTYwYmY5NDYiLCJleHAiOjE1NzExOTUyNzksImF1dGhvcml0aWVzIjpbImFkbWluIl0sImp0aSI6IjU1NTRmYjdkLTBhZGItNGI4MS1iOGNlLWIwOTk2NjM1OTI4MCIsImNsaWVudF9pZCI6ImNsaWVudDEiLCJ1c2VybmFtZSI6IjkwMDEifQ.TA1frc46XRkNgl3Y_n72rM0nZ5QceWH3zJFmR7CkHQ4", "expires_in": 43199, "scope": "read", "username": "9001", "jti": "f9d0a6fa-019f-4e97-b2b8-b593e60bf946" }

2, Cipher Mode : grant_type = password

Postman:
http://localhost:9090/oauth/token?username=user&password=123456&grant_type=password&scope=read&client_id=client1&client_secret=123456

curl:

curl -X POST \
  'http://localhost:9090/oauth/token?username=user&password=123456&grant_type=password&scope=read&client_id=client1&client_secret=123456' \
  -H 'Accept: */*' \
  -H 'Accept-Encoding: gzip, deflate' \
  -H 'Cache-Control: no-cache' \
  -H 'Connection: keep-alive' \
  -H 'Content-Length: ' \
  -H 'Cookie: remember-me=; JSESSIONID=F6F6DE2968113DDE4613091E998D77F4' \
  -H 'Host: localhost:9090' \
  -H 'Postman-Token: f41c7e67-1127-4b65-87ed-21b3e00cfae3,08168e2e-1818-42f8-b4c4-cafd4aa0edc4' \
  -H 'User-Agent: PostmanRuntime/7.16.3' \
  -H 'cache-control: no-cache'
  

3, client mode : grant_type = client_credentials

Postman:

   localhost:9090/oauth/token?scope=read&grant_type=client_credentials

   Note to fill client information Authorization, following a curl request:

curl:

curl -X POST \
  'http://localhost:9090/oauth/token?scope=read&grant_type=client_credentials' \
  -H 'Accept: */*' \
  -H 'Accept-Encoding: gzip, deflate' \
  -H 'Authorization: Basic Y2xpZW50MToxMjM0NTY=' \
  -H 'Cache-Control: no-cache' \
  -H 'Connection: keep-alive' \
  -H 'Content-Length: 35' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -H 'Cookie: remember-me=; JSESSIONID=F6F6DE2968113DDE4613091E998D77F4' \
  -H 'Host: localhost:9090' \
  -H 'Postman-Token: a8d3b4a2-7aee-4f0d-8959-caa99a412012,f5e41385-b2b3-48d2-aa65-8b1d1c075cab' \
  -H 'User-Agent: PostmanRuntime/7.16.3' \
  -H 'cache-control: no-cache' \
  -d 'username=zhoutaoo&password=password'

   Authorized service development, we continue to extend security configuration prior to use, including UserDetailsService its landing configuration on its basis we have added authorization, to complete the build and test the entire authorization service.

Third, the development of server resources

  Since the resource server application is a privilege, our new security-oauth2-authentication sub-module as a resource server applications.

A, Maven relies

   security-oauth2-authentication module pom reference security-core:

       <dependency>
           <groupId>com.zhc</groupId>
           <artifactId>security-core</artifactId>
           <version>0.0.1-SNAPSHOT</version>
           <exclusions>
               <exclusion>
                   <groupId>org.springframework.security.oauth</groupId>
                   <artifactId>spring-security-oauth2</artifactId>
               </exclusion>
           </exclusions>
       </dependency>

       <!-- 不是starter,手动配置 -->
       <dependency>
           <groupId>org.springframework.security.oauth</groupId>
           <artifactId>spring-security-oauth2</artifactId>
           <!--请注意下 spring-authorization-oauth2 的版本 务必高于 2.3.2.RELEASE,这是官方的一个bug:
           java.lang.NoSuchMethodError: org.springframework.data.redis.connection.RedisConnection.set([B[B)V
           要求必须大于2.3.5 版本,官方解释:https://github.com/BUG9/spring-security/network/alert/pom.xml/org.springframework.security.oauth:spring-security-oauth2/open
           -->
           <version>2.3.5.RELEASE</version>
       </dependency>
Second, configure authorized service @EnableResourceServer

   Configuration of the entire resource services in three main points:

  • @EnableResourceServer must be, the foundation of the entire resources of the server
  • tokenStore due to licensing server uses a different tokenStore, so we have to parse token according to the configuration of the storage policy
  • HttpSecurity in general as long as the resource server, its internal interfaces are only accessible after authentication is required, the following simple configuration here.
@Configuration
@EnableResourceServer  // 1
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

   private final Map<String,TokenStore> tokenStoreMap;

   @Value("${spring.security.oauth2.storeType}")
   private String storeType = "jwt";

   @Autowired
   public ResourceServerConfiguration(Map<String, TokenStore> tokenStoreMap) {
       this.tokenStoreMap = tokenStoreMap;
   }

   @Override
   public void configure(ResourceServerSecurityConfigurer resources) {
       resources.tokenStore(tokenStoreMap.get(storeType + "TokenStore"));  //  2
   }

   @Override
   public void configure(HttpSecurity http) throws Exception {
       http
               .requestMatchers().anyRequest()
               .and()
               .anonymous()
               .and()
               .authorizeRequests()
               //配置oauth2访问(测试接口)控制,必须认证过后才可以访问
               .antMatchers("/oauth2/**").authenticated();  // 3 
   }
}
Third, the configuration application.yml

   Since the authorization server using different tokenStore, so here also cited its configuration:

spring:
 redis:
   host: 127.0.0.1
   port: 6379
 security:
   oauth2:
     storeType: jwt
     jwt:
       SigningKey: oauth2
Fourth, the test interface

@RestController
@RequestMapping("/oauth2")
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Slf4j
public class TestEndpoints {

    @GetMapping("/getUser")
    @PreAuthorize("hasAnyAuthority('user')")
    public String getUser() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return "User: " + authentication.getPrincipal().toString();
    }
}
Fifth, start the test

   We'll get to the token authorization server access test interface:

Postman:
http://localhost:8090/oauth2/getUser?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJyZWFkIl0sImV4cCI6MTU2ODY0ODEyMCwianRpIjoiNDQ0NWQ1ZDktYWZlMC00N2Y1LTk0NGItZTEyNzI1NzI1M2M1IiwiY2xpZW50X2lkIjoiY2xpZW50MSIsInVzZXJuYW1lIjoiY2xpZW50MSJ9.pOnIcmjy2ex7jlXvAGslEN89EyFPYPbW-l4f_cyK17k

curl:
```

curl -X GET 'http://localhost:8090/oauth2/getUser?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJyZWFkIl0sImV4cCI6MTU2ODY0ODEyMCwianRpIjoiNDQ0NWQ1ZDktYWZlMC00N2Y1LTk0NGItZTEyNzI1NzI1M2M1IiwiY2xpZW50X2lkIjoiY2xpZW50MSIsInVzZXJuYW1lIjoiY2xpZW50MSJ9.pOnIcmjy2ex7jlXvAGslEN89EyFPYPbW-l4f_cyK17k' -H 'Accept: /' -H 'Accept-Encoding: gzip, deflate' -H 'Cache-Control: no-cache' -H 'Connection: keep-alive' -H 'Cookie: remember-me=; JSESSIONID=F6F6DE2968113DDE4613091E998D77F4' -H 'Host: localhost:8090' -H 'Postman-Token: 07ec53c7-9051-439b-9603-ef0fe93664fa,e4a5b46e-feb7-4bf8-ab53-0c33aa44f661' -H 'User-Agent: PostmanRuntime/7.16.3' -H 'cache-control: no-cache'

```

Fourth, personal summary

   Spring security Oauth2 is a standard set of Oauth2 achieve, we can develop a better understanding Oauth2, but the technique involves a whole is still a lot, such as redis, jwt and so on. This article is just a simple demonstration of Spring security Oauth2 Demo, want to help you, if you also want in-depth analysis at Spring Security Oauth2, then please continue to focus on me, follow-up will resolve its principles.

   This article describes Spring security Oauth2 developed code can access code repository, github address projects: https://github.com/BUG9/spring-security

         If you are interested in these, welcomed the star, follow, bookmarking, forwarding support!

Guess you like

Origin www.cnblogs.com/bug9/p/11526968.html