SpringCloudOauth2

Oauth2 profile

OAuth 2.0 is an industry standard protocol for authorization. OAuth 2.0 aims to simplify client-side developers while providing specific authorization processes for Web applications, desktop applications, mobile phones, and living room devices. The specification and its extensions are being developed within the IETF OAuth working group.

The standard for OAuth 2.0 is RFC 6749 files. The file begins by explaining what OAuth is. The core of **OAuth is to issue tokens to third-party applications. ** RFC 6749 then goes on to say that it defines four authorization grants for obtaining tokens.

Authorization Flow Chart

+--------+ +---------------+ | |--(A)- Authorization Request ->| Resource | | | | Owner | | |<-(B)-- Authorization Grant  ---| | | | +---------------+ | | | | +---------------+ | |--(C)-- Authorization Grant -->| Authorization | | Client | |  Server | | |<-(D)----- Access Token -------| | | | +---------------+ | | | | +---------------+ | |--(E)----- Access Token ------>| Resource | | | | Server | | |<-(F)--- Protected Resource ---| | +--------+ +---------------+Copy the code

The simple claim is to initiate an authentication request, to authenticate the authentication server based on the authorization type, return the token if successful, then use the token to access the resource server, and return the protected resource if the token passes the authentication.

Included roles

OAuth defines four roles:

** Resource owner: ** The entity that can grant access to a protected resource. When the owner of a resource is a person, it is called an end user.

** Resource server: ** A server that hosts protected resources and is able to accept and respond to protected resource requests with access tokens.

** Client: ** For our products, QQ, wechat login is a third-party login system. We also need the resources (avatar, nickname, etc.) of the third party to log in to the system.

** Authorization server: ** After a successful authorization request, the server issues an access token to the client to authenticate the resource owner and obtain authorization

Authorization model

  • Authorization-code

    • This approach is the most common process and the most secure, and is suitable for Web applications that have a back end. Authorization codes are transmitted through the front end, tokens are stored in the back end, and all communication with the resource server is done in the back end. This separation of the front and back ends prevents token leakage.

      In the first step, website A provides A link, and users will jump to website B after clicking, and authorize user data to website A for use. The following is A schematic link from A to B.

      https:// Server IP: server PORT/oauth/authorize?Response_type =code& # client_id= client_id & # client_id redirect_uri=CALLBACK_URL& # CALLBACK_URL Scope =read # Not requiredCopy the code
    • Second, after the user jumps, website B will ask the user to log in, and then ask whether they agree to authorize website A. The user agrees, and site B jumps back to the url specified by the redirect_URI parameter. When a jump occurs, an authorization code is returned, as shown below.

      https://CALLBACK_URL? code=AUTHORIZATION_CODE
      Copy the code
    • Third, after site A gets the authorization code, it can request the token from site B at the back end.

      https:// Server IP: server PORT/oauth/token?
       client_id=CLIENT_ID&
       client_secret=CLIENT_SECRET&
       grant_type=authorization_code&
       code=AUTHORIZATION_CODE&
       redirect_uri=CALLBACK_URL
      Copy the code

      In the above URL, the client_id and client_secret parameters are used to allow B to confirm the identity of A (the client_secret parameter is secret, so only requests can be made at the back end), and the grant_type parameter is authorization_code, The code parameter is the authorization code obtained in the previous step, and the redirect_URI parameter is the callback url after the token is issued.

    • Step 4: After B receives the request, it issues a token.

      {
          "access_token": "0e6d26f3-3ad3-407f-8203-3342f25807ca"."token_type": "bearer"."refresh_token": "9941dc19-a167-4173-9895-934f9519363d"."expires_in": 86399."scope": "all"."username1": "demo"."license": "wujie"
      }
      Copy the code
  • Implicit

    • Some Web applications are pure front-end applications without a back end. In this case, you can’t use the above method and must store the token in the front end. RFC 6749 provides a second approach that allows tokens to be issued directly to the front end. This approach has no intermediate step called an authorization code, so it is called “implicit.”

    • In the first step, website A provides A link requiring users to jump to website B and authorizes user data to be used by website A

    • https:// Server IP: server PORT/oauth/authorize?Client_id = client_id & redirect_URI =CALLBACK_URL& scope=read; response_type=token& #response_typeCopy the code
    • In the second step, the user jumps to website B and agrees to authorize website A after logging in. In this case, site B will jump back to the redirect URL specified by the redirect_URI parameter and pass the token to Site A as the URL parameter.

    • https:// server IP: server PORT/callback#token=ACCESS_TOKEN #token=ACCESS_TOKEN
      Copy the code

      Note that the token is in a FRAGMENT of a URL, not a querystring. This is because OAuth 2.0 allows URL hops to HTTP, so there is a risk of “manin the middle” attacks. When the browser hops, the anchor will not be sent to the server. The risk of token leakage is reduced.

      This method has low security, so it is generally used in scenarios that do not require high security. In addition, the validity period of the token is short, that is, the current session, and the token is invalid when the session ends.

  • Password:

    • RFC 6749 also allows users to give a user name and password directly to an application if you have high trust in it. The app uses your password to request a token, which is called “password”.

    • In the first step, site A requires users to provide the username and password of Site B. After receiving it, A directly requests the token from B.

    • https:// Server IP: server PORT/oauth/token?Grant_type =password& #password specifies the password mode. Username = username & # username password= password& #password client_id= client_id # specifies the clientCopy the code
    • Step 2: After B website verifies the identity, it directly gives the token. Notice that instead of jumping, you put the token inside the JSON data as an HTTP response, and A gets the token.

    • {
          "access_token": "0e6d26f3-3ad3-407f-8203-3342f25807ca"."token_type": "bearer"."refresh_token": "9941dc19-a167-4173-9895-934f9519363d"."expires_in": 86399."scope": "all"."username1": "demo"."license": "wujie"
      }
      Copy the code
    • After all, this method directly gives the user’s account password, which is best used in the internal scenario of your own system. If not, it has to be a high level of mutual trust

  • Client credentials

    • For command line applications without a front end, that is, a token is requested at the command line.

    • In the first step, application A makes A request to application B at the command line.

      https:// Server IP: server PORT/oauth/token?Grant_type =client_credentials& # Specifies the use of the client credential mode client_id= client_ID & # client client_secret= client_secret #Copy the code
    • Step 2: After B passes the verification, the token is returned directly.

      The token presented in this way is for the third-party application, not for the user, i.e. multiple users may share the same token.

Note that in either authorization mode, third-party applications must register with the system before applying for a token, state their identity, and receive two identifiers: client ID and Client secret. This is to prevent misuse of tokens, and third-party applications that have not been registered will not get tokens.

The refresh token

OAuth 2.0 allows users to update tokens automatically. When the token expires, it would be quite unfriendly to ask users to go through the above process again.

{
    "access_token": "0e6d26f3-3ad3-407f-8203-3342f25807ca"."token_type": "bearer"."refresh_token": "9941dc19-a167-4173-9895-934f9519363d"."expires_in": 86399."scope": "all"."username1": "demo"."license": "wujie"
}
Copy the code

In the tokens we requested above, both access_token and refresh_token were issued. Before the token expires, the user uses refresh Token to issue a request to update the token.

https:// Server IP: server PORT/oauth/token?Grant_type =refresh_token& # Specifies the secret defined by the refresh token client_id= client_id & # Refresh_token = refresh_token # Use refresh_token that is returned at the same time the token is obtainedCopy the code

B once the site is authenticated, a new token is issued.

Spring Security

Introduction to the

Spring Security is a powerful and highly customizable authentication and access control framework. It is the de facto standard for securing Spring-based applications.

Spring Security is a framework dedicated to providing authentication and authorization for Java applications. As with all Spring projects, the real power of Spring Security is that it can be easily extended to meet custom requirements

The characteristics of

  1. Comprehensive and extensible support for authentication and authorization
  2. Prevent attacks such as session fixation, click hijacking, cross-site request forgery, etc
  3. The Servlet API integration
  4. Optional with Spring Web MVC

Spring Cloud Security

Introduction to the

Spring Cloud Security provides a set of primitives for building secure applications and services with minimal hassle. Declarative models that can be heavily configured externally (or centrally) can often implement large, collaborative remote component systems through central identity management services. It is also easy to use in service platforms such as Cloud Foundry. Based on Spring Boot and Spring Security OAuth2, we can quickly create systems that implement common patterns such as single sign-on, token relay, and token exchange.

The characteristics of

  1. Relay the SSO token from the front end to the back end service in the Zuul proxy
  2. Relay tokens between resource servers
  3. Interceptors that make Feign clients behave like OAuth2RestTemplate (get tokens, etc.)
  4. Configure downstream authentication in the Zuul proxy

Spring Security Oauth2 is no longer supported by Spring Security Oauth2. They will migrate the Oauth2, in other words, they will build their own.

Those who are interested can read about it by themselves.

Let’s get down to business. Using the SpringCloudAlibaba framework we built earlier, we will create a new project.

Creating an Authorization Service

The configuration file of pom.xml is as follows


      
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.wujie</groupId>
        <version>0.0.1 - the SNAPSHOT</version>
        <artifactId>hello-spring-cloud-alibaba-dependencies</artifactId>
    </parent>
    <groupId>com.wujie</groupId>
    <artifactId>hello-spring-cloud-alibaba-nacos-oauth-authorization</artifactId>
    <version>0.0.1 - the SNAPSHOT</version>
    <name>hello-spring-cloud-alibaba-nacos-oauth-authorization</name>
    <description>Creating an Authorization Server</description>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <! -- Freemarker, page rendering engine -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>2.2.1. RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
            <version>2.2.4. RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.3.3. RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>
        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
            <version>2.7.9</version>
        </dependency>
        <! -- Mybatis launcher -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.1</version>
        </dependency>
        <! -- Mapper launcher -->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>2.1.5</version>
        </dependency>
        <! -- Page Assistant launcher -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.3</version>
        </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>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.0. RELEASE</version>
                <configuration>
                    <mainClass>
                        com.wujie.hello.spring.cloud.alibaba.nacos.oauth.authorization.HelloSpringCloudAlibabaNacosOauthAuthorizationApplication
                    </mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>
Copy the code

We will introduce spring-cloud-starter-oAuth2 directly here. Since the package already includes spring-Cloud-starter-Security, there is no need to introduce it separately. We also added a mysql driver package because we want to use the database to manage the client, and a tk-Mybatis package to be a little more formal to get user information and permissions from the database. Redis packages have also been introduced to store tokens.

application.yml

spring:
  application:
    name: hello-spring-cloud-alibaba-nacos-oauth-authorization
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
  freemarker:
    suffix: .ftl   # SpringBoot2.2 and later use FTLH by default and will not find FTL if not configured
  redis:
    host: 127.0. 01.
    port: 6379
  management:
    endpoints:
      web:
        exposure:
          include: "*"
  main:
    allow-bean-definition-overriding: true
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: Database account
    password: Database password
    hikari:
      maximum-pool-size: 30
      minimum-idle: 10
      auto-commit: true
      idle-timeout: 30000
      pool-name: DatebookHikariCP
      max-lifetime: 1800000
      connection-timeout: 30000
      connection-test-query: SELECT 1
      jdbc-url: jdbc:mysql://localhost:3306/oauth? useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
    url: jdbc:mysql://localhost:3306/oauth? useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
server:
  port: 8766
logging.level.org.springframework.security: DEBUG
Copy the code

Create an authorization profile

According to the official instructions we need to create a configuration class to realize AuthorizationServerConfigurer so we create a class to inherit its implementation class AuthorizationServerConfigurerAdapter, specific code is as follows:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private DataSource dataSource;
    @Autowired
    UserDetailsServiceImpl userDetailsServiceImpl;

    /** * tokenStore is redis@return* /
    @Bean
    public TokenStore tokenStore(a){
        return new RedisTokenStore(redisConnectionFactory);
    }



    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        // Configure to allow form access
        security.allowFormAuthenticationForClients().tokenKeyAccess("isAuthenticated()")
                .checkTokenAccess("permitAll()");
    }

    /** * Config client management is JDBC *@param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(new JdbcClientDetailsService(dataSource));
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer()));
        endpoints.authenticationManager(authenticationManager)
                .tokenEnhancer(tokenEnhancerChain)
                // Configure tokenStore management and client configuration
                .tokenStore(tokenStore()).userDetailsService(userDetailsServiceImpl)
                // Configure the authorization mode
                .tokenGranter(tokenGranter(endpoints));
        // Configure tokenServices parameters
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        // Set the accessToken expiration time
        defaultTokenServices.setAccessTokenValiditySeconds((int) TimeUnit.HOURS.toSeconds(2));
        // Configure the expiration time of refreshToken
        defaultTokenServices.setRefreshTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30));
        // Set the support for refreshing tokens
        defaultTokenServices.setReuseRefreshToken(true);
        defaultTokenServices.setSupportRefreshToken(true);
        defaultTokenServices.setTokenStore(endpoints.getTokenStore());
        defaultTokenServices.setClientDetailsService(endpoints.getClientDetailsService());
        defaultTokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
        endpoints.tokenServices(defaultTokenServices);
    }

    / * * * configure authorization model can also add custom (not write also have default) * * to see AuthorizationServerEndpointsConfigurer getDefaultTokenGranters in method Later add a mobile phone verification code function * *@param endpoints
     * @return* /
    private TokenGranter tokenGranter(AuthorizationServerEndpointsConfigurer endpoints) {
            List<TokenGranter> list = new ArrayList<>();
            // Add a refresh token
            list.add(newRefreshTokenGranter(endpoints.getTokenServices(),endpoints.getClientDetailsService(),endpoints.getOAuth2RequestFactory() ));// Authorization code mode
            list.add(newAuthorizationCodeTokenGranter(endpoints.getTokenServices(),endpoints.getAuthorizationCodeServices(),endpoints.getClientD etailsService(),endpoints.getOAuth2RequestFactory()));// Client credential mode
            list.add(newClientCredentialsTokenGranter(endpoints.getTokenServices(),endpoints.getClientDetailsService(),endpoints.getOAuth2Reques tFactory()));// Password mode
            list.add(newResourceOwnerPasswordTokenGranter(authenticationManager,endpoints.getTokenServices(),endpoints.getClientDetailsService() ,endpoints.getOAuth2RequestFactory()));/ / hidden
            list.add(newImplicitTokenGranter(endpoints.getTokenServices(),endpoints.getClientDetailsService(),endpoints.getOAuth2RequestFactory( )));return new CompositeTokenGranter(list);
    }

    /** * Create a token enhancement method to add some additional information that we want to return ourselves *@return* /
    @Bean
    public TokenEnhancer tokenEnhancer(a) {
        return (accessToken, authentication) -> {
            final Map<String, Object> additionalInfo = new HashMap<>(2);
            additionalInfo.put("license"."wujie");
            UserDetailsDto user = (UserDetailsDto) authentication.getUserAuthentication().getPrincipal();
            if(user ! =null) {
                additionalInfo.put("phone", user.getPhone());
                additionalInfo.put("id", user.getId());
            }
            ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
            returnaccessToken; }; }}Copy the code

Since the authorization server is itself a resource server, create a resource configuration as well, with the following code

@Configuration
// Enable the resource service
@EnableResourceServer
// Enable method-level permission control
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Log4j2
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    HTTP. AuthorizeRequests () = HTTP. AuthorizeRequests () = HTTP. AuthorizeRequests () = HTTP. Instead of http.authorizerequests (), * instead uses requestMatchers().antmatchers (""), which is configured with an array of urls that need to be intercepted by the resource interface@param http
     * @return void
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http    // Configure the resource interface to be protected
                .requestMatchers().antMatchers("/user"."/test/need_token"."/logout"."/remove"."/update"."/test/need_admin"."/test/scope") .and().authorizeRequests().anyRequest().authenticated(); }}Copy the code

Then create a webSecurity configuration with the following code

@Configuration
@EnableWebSecurity()
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    @Autowired
    private IgnoreLogoutFilter ignoreLogoutFilter;
    /** * Configure password encryption *@return* /
    @Bean
    public PasswordEncoder passwordEncoder(a){
        return new BCryptPasswordEncoder();
    }
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean(a) throws Exception {
        return super.authenticationManagerBean();
    }

    /** * Security request configuration, where the Security part of the request is passed, Security interception in the resource server configuration *@param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // Configure allowed requests and cross-domain issues
        http
                .formLogin().loginPage("/login")
                .permitAll()
                .and().authorizeRequests().anyRequest().permitAll()
                .and().csrf().disable().cors()
                .and().addFilterAt(ignoreLogoutFilter, LogoutFilter.class);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // Configure the user information and encryption mode
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        // The configuration ignores some static resources
        web.ignoring().antMatchers("/css/**"."/static/**"); }}Copy the code

Finally, create a Controller for access testing

@RestController
@Slf4j
public class UserController {

    @GetMapping("/user")
    public Object userInfo(Principal user, Authentication authentication) {
        log.info("user:{}",user);
        log.info("auth:{}", authentication);
        return  user;
    }
    /** * Authentication page *@return ModelAndView
     */
    @GetMapping("/login")
    public ModelAndView require(a) {
        log.info("-- Authentication page --");
        return new ModelAndView("ftl/login");
    }
    @PreAuthorize("hasAuthority('ROLE_USER')")
    @GetMapping("/test/need_admin")
    public @ResponseBody String admin(a) {
        return "need_admin"; }}Copy the code

Request with token:

Request without token

With a token, but without the ROLE_ADMIN permission

Those of you who are more careful will notice that the port number I used here is not the same as the configured one. This is because I used the gateway for unified entry access. We introduced the spring-cloud-starter-OAuth2 dependency on top of our previous project. Add the following configuration to the application.yml configuration file:

## Security configuration ##
security:
  oauth2:
    resource:
      user-info-uri: http://localhost:8766/user
      prefer-token-info: false
Copy the code

The last step plus our resource allocation:

@Configuration
@EnableResourceServer
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Log4j2
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Autowired
    private UserInfoTokenServices userInfoTokenServices;


    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // The configuration does not require secure url interception
                .antMatchers("/test/no_need_token").permitAll()
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                .anyRequest().authenticated();

    }

    /** ** If the client does not have the same configuration, it cannot access *@param resources
     * @throws Exception
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources/*.resourceId(RESOURCE_ID)*/.stateless(true); 
        resources.tokenServices(userInfoTokenServices);
    }

Copy the code

The Fegin service Token is lost

In microservices, RestTemplate or Fegin is often used to call between services, which leads to a problem. When we call other services, the token will be lost and we have no permission to access them.

So we need to add some interceptors to take our tokens with us.

The configuration code for Fegin is as follows:

public class FeignRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        assertattributes ! =null;
        HttpServletRequest request = attributes.getRequest();

        // Set the request header
        Enumeration<String> headerNames = request.getHeaderNames();
        if(headerNames ! =null) {
            while(headerNames.hasMoreElements()) { String name = headerNames.nextElement(); String value = request.getHeader(name); requestTemplate.header(name, value); }}// Set the request body, in this case mainly to pass access_token
        Enumeration<String> parameterNames = request.getParameterNames();
        StringBuilder body = new StringBuilder();
        if(parameterNames ! =null) {
            while (parameterNames.hasMoreElements()) {
                String name = parameterNames.nextElement();
                String value = request.getParameter(name);

                // Add the Token to the request header
                if ("access_token".equals(name)) {
                    requestTemplate.header("authorization"."Bearer " + value);
                }

                // Add other parameters to the request body
                else {
                    body.append(name).append("=").append(value).append("&"); }}}// Set the request body
        if (body.length() > 0) {
            // Remove the last ampersand
            body.deleteCharAt(body.length() - 1); requestTemplate.body(body.toString()); }}}Copy the code

Then add this interceptor to our Fegin request interceptor:

@Configuration
public class FeignRequestConfiguration {

    @Bean
    public RequestInterceptor requestInterceptor(a) {
        return newFeignRequestInterceptor(); }}Copy the code

The service Token of the RestTemplate is lost

Our interceptor for RestTemplate could be written like this:

@Component
public class TokenInterceptor implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
            throws IOException {
        HttpHeaders headers = request.getHeaders();
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)auth.getDetails();

        // Add a custom field
        headers.add("Authorization", details.getTokenType() + "" + details.getTokenValue());

        // Ensure that the request continues to be executed
        returnexecution.execute(request, body); }}Copy the code

And the same goes for the RestTemplate interceptor

@Bean
public RestTemplate restTemplate(a)
{
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.setInterceptors(Collections.singletonList(tokenInterceptor));
    return restTemplate;
}
Copy the code

Then restart our project and we can successfully invoke our service.

conclusion

For security authentication frameworks, Shiro and SpringSecurity are commonly used.

In my opinion, Shiro has a huge advantage over Spring Security in terms of simplicity and flexibility while maintaining powerful features.

Shiro has the following advantages:

  1. An easy-to-understand Java Security API;
  2. Simple authentication (login), support for multiple data sources (LDAP, JDBC, Kerberos, ActiveDirectory, etc.);
  3. Simple sign-off (access control) for roles, supporting fine-grained sign-off;
  4. Support level 1 caching to improve application performance;
  5. Built-in POJO-based enterprise session management for Both Web and non-Web environments;
  6. Heterogeneous client session access;
  7. Very simple encryption API;
  8. Does not bind to any framework or container and can run independently.

Shiro has three core components: Subject, SecurityManager, and Realms.

Subject: the Subject can be any “user” that can interact with the application.

SecurityManager: equivalent to SpringMVC’s DispatcherServlet or Struts2’s FilterDispatcher; Is Shiro’s heart; All specific interactions are controlled through the SecurityManager; It manages all subjects and is responsible for authentication and authorization, as well as session and cache management.

Realm: Shiro obtains security data from realms (users, roles, and permissions). To authenticate users, The SecurityManager needs to obtain the corresponding users from realms for comparison. You also need to get the user’s role/permissions from Realm to verify that the user can operate. You can think of a Realm as a DataSource.

Spring Security and Shiro

Similarities:

1: indicates authentication

2: authorization function

3: encryption function

4: Session management

5: Cache support

6: rememberMe function…….

Difference:

Advantages:

1. Spring Security is developed based on Spring. If the project uses Spring as the basis, it is more convenient to do permissions with Spring Security, while Shiro needs to integrate development with Spring

2. Spring Security has more features than Shiro, such as Security protection

3: The Spring Security community has more resources than Shiro

Disadvantages:

1: Shiro is relatively simple to configure and use, while Spring Security is complicated to get started

2: Shiro has low dependency and does not require any framework or container and can run independently, while Spring Security relies on the Spring container

Personal insight

I feel it is difficult to study this time, and I have the following doubts:

  1. The information on the official website is relatively deep and difficult to dig, how to find the corresponding technology in a faster way
  2. The official website information is some of the more one-sided, how to know how to write a specific configuration file
  3. How should specific methods be rewritten in the configuration file, and what does the specific method do
  4. The last one is more like knowing how the first person to implement a technology did it

I hope someone can help me answer it.

The ancients said that reviewing the old and knowing the new can be a teacher

Appendix Database table

-- used in tests that use HSQL
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));create table oauth_client_token (
  token_id VARCHAR(256),
  token LONGVARBINARY,
  authentication_id VARCHAR(256) PRIMARY KEY,
  user_name VARCHAR(256),
  client_id VARCHAR(256));create table oauth_access_token (
  token_id VARCHAR(256),
  token LONGVARBINARY,
  authentication_id VARCHAR(256) PRIMARY KEY,
  user_name VARCHAR(256),
  client_id VARCHAR(256),
  authentication LONGVARBINARY,
  refresh_token VARCHAR(256));create table oauth_refresh_token (
  token_id VARCHAR(256),
  token LONGVARBINARY,
  authentication LONGVARBINARY
);

create table oauth_code (
  code VARCHAR(256), authentication LONGVARBINARY
);

create table oauth_approvals (
	userId VARCHAR(256),
	clientId VARCHAR(256),
	scope VARCHAR(256),
	status VARCHAR(10),
	expiresAt TIMESTAMP,
	lastModifiedAt TIMESTAMP
);


-- customized oauth_client_details table
create table ClientDetails (
  appId VARCHAR(256) PRIMARY KEY,
  resourceIds VARCHAR(256),
  appSecret VARCHAR(256),
  scope VARCHAR(256),
  grantTypes VARCHAR(256),
  redirectUrl VARCHAR(256),
  authorities VARCHAR(256),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additionalInformation VARCHAR(4096),
  autoApproveScopes VARCHAR(256));Copy the code

The resources