Solve the single sign-on (SSO) problem after Spring Boot upgraded from 1.x to 2.x

Keywords: Java Spring Session Maven github

Solve the single sign-on (SSO) problem after Spring Boot upgraded from 1.x to 2.x

When learning Spring Cloud, when encountering the related content of authorized service oauth, he always knows half of it, so he decided to study and collate the content, principle and design of Spring Security, Spring Security Oauth2 and other permissions, authentication-related content. This series of articles is written to enhance the impression and understanding in the process of learning. If there is any infringement, please let me know.

Project environment:

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

Previously, Spring Security related content has almost been written, so I recently sorted out Spring Sexuality Oauh2 related content, but when I went to single sign-on (OSS), there was a problem that has been bothering me for a long time, because of the online upgrade of Spring Boot 1.x to Spring Boot 2.x and then single point. There is very little information about landing related problem solving, so here is a special article to describe the problems encountered in the upgrade process, the problem manifestation phenomenon and how I solve these problems.

Question 1: Sprboot 2 removes @EnableOAuth2Sso?

First of all, let me tell you very clearly, no!! But why did the introduction of spring-security-oauth2 maven rely on IDEA prompt @Enable OAuth2Sso not find it? First we find out. Official Spring Boot 2.x Upgrade Document We will find out about Oauth2:

OAuth 2.0 Support
Functionality from the Spring Security OAuth project is being migrated to core Spring Security. OAuth 2.0 client support has already been added and additional features will be migrated in due course.

If you depend on Spring Security OAuth features that have not yet been migrated you will need to add org.springframework.security.oauth:spring-security-oauth2 and configure things manually. If you only need OAuth 2.0 client support you can use the auto-configuration provided by Spring Boot 2.0. We're also continuing to support Spring Boot 1.5 so older applications can continue to use that until an upgrade path is provided.

_We can roughly understand that official 2.x is migrating Spring Security OAuth project functionality to Spring Security. But most notable is that If you only need OAuth 2.0 client support you can use the auto-configuration provided by Spring Boot 2.0. (If you want to use Oauth2 client-related functions in Spring Boot 2.0 (and above), you need to use auto-configuration.)

According to this prompt, we can find it. Official auto-configuration document Come in and tell us the minimum maven dependencies we need to use:

        <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.7.RELEASE</version>
        </dependency>

_successfully quoted @EnableOAuth2Sso according to the official document configuration, so far, the problem has been solved!

Question 2: Callback to the client in the process of single login authorization but prompt 401 (unauthorized) question?

SSO Client Related Configuration

Client Security Config configuration
@Configuration
@EnableOAuth2Sso  // SSo Automatic Configuration Reference
public class ClientSecurityConfig extends WebSecurityConfigurerAdapter {

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

}

This configuration follows Official auto-configuration document Recommended configuration.

application.yml configuration
auth-server: http://Localhost: 9090# authorization service address


security:
  oauth2:
    client:
      user-authorization-uri: ${auth-server}/oauth/authorize #Address Requesting Authentication
      access-token-uri: ${auth-server}/oauth/token #The address of the request token
    resource:
      jwt:
        key-uri: ${auth-server}/oauth/token_key #Resolve the address of the key required by the jwt token. When the service starts, the authorization service will be called to obtain the jwt key. So it is necessary to ensure that the authorization service is normal.
    sso:
      login-path: /login #The path to the login page, where the OAuth2 authorization server triggers redirection to the client, defaults to / login
spring:
  profiles:
    active: client1

_Because we need multi-client single-point testing, here we use Spring boot multi-environment configuration, where the configuration of authorization services is not described, and the default build of an available authorization service (if you do not know how to build Oauth2 authorization services and resource services, you can pay attention to me, the follow-up will be relevant. Article).

Application-client 1.yml configuration
    server:
      port: 8091
    
    security:
      oauth2:
        client:
          client-id: client1
          client-secret: 123456
Test interface
@RestController
@Slf4j
public class TestController {

    @GetMapping("/client/{clientId}")
    public String getClient(@PathVariable String clientId) {
        return clientId;
    }

}

So far, we have completed a basic SSO client and started the project.

Problem Description and Phenomena

_Browser access test interface localhost:8091/client/1, jump to authorization service landing interface, after successful landing, jump back to the client/login address (that is, spring.security.sso.login-path we configured), under normal circumstances will again jump to localhost:8091/client/1 (this has been authenticated). Visit after work. The whole process is the authorization code mode process of Oauth2. But now there is a problem that when the authorization service calls back to the client's / login address, the browser displays HTTP ERROR 401, as follows:

From the figure, we can see that the authorization service successfully returned the authorization code, but because of the problems of our client, 401 occurred, which caused the whole authorization code mode process to be interrupted. Look at Official auto-configuration document In the process, unintentional discovery

Also note that since all endpoints are secure by default, this includes any default error handling endpoints, for example, the endpoint "/error". This means that if there is some problem during Single Sign On that requires the application to redirect to the "/error" page, then this can cause an infinite redirect between the identity provider and the receiving application.
First, think carefully about making an endpoint insecure as you may find that the behavior is simply evidence of a different problem. However, this behavior can be addressed by configuring the application to permit "/error":

@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/error").permitAll()
                .anyRequest().authenticated();
    }
}

_roughly means that since all endpoints are secure by default, this includes any default error handling endpoints, such as endpoint "/ error". This means that if there are some problems during single sign-on, requiring the application to redirect to the'/ error'page, this will lead to an infinite redirection between the identity provider and the receiving application.

According to this prompt, I started DEBUG. As the document says, there are some problems redirected to / error during single sign-on. So we configure / error as unauthorized access and restart the test interface. This error interface prompt is obvious:

_Since there is a clear indication of Unauthorized, let's take a step-by-step DEBUG to see what's wrong during the single point period.

III. Problem Investigation and Solution

From the description of previous phenomena, we can know that the problem point is calling to get token after the authorization code comes back. Then according to the source code, the step of getting token is inside the OAuth2Client Authentication Processing Filter filter. The key code is as follows:

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException, IOException, ServletException {

        OAuth2AccessToken accessToken;
        try {
            accessToken = restTemplate.getAccessToken();  // 1 Call authorization service to get token 
        } catch (OAuth2Exception e) {
            BadCredentialsException bad = new BadCredentialsException("Could not obtain access token", e);
            publish(new OAuth2AuthenticationFailureEvent(bad));
            throw bad;          
        }
        try {
            OAuth2Authentication result = tokenServices.loadAuthentication(accessToken.getValue());  // After success, parse OAuth 2Authentication information from token
            if (authenticationDetailsSource!=null) {
                request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, accessToken.getValue());
                request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, accessToken.getTokenType());
                result.setDetails(authenticationDetailsSource.buildDetails(request));
            }
            publish(new AuthenticationSuccessEvent(result));
            return result;
        }
        catch (InvalidTokenException e) {
            BadCredentialsException bad = new BadCredentialsException("Could not obtain user details from token", e);
            publish(new OAuth2AuthenticationFailureEvent(bad));
            throw bad;          
        }

    }

_We hit the breakpoint here, under Debug, as expected, when we got token, the exception information was: Possible CSRF detected - state parameter was required but no state could be found, debug screenshot is as follows:

_There is a statement about consulting online materials:

Local development, auth server and client are both local hosts, resulting in the interaction of JSESSIONID. This can be done by configuring the context-path or session name of the client

According to this description, I try to solve the problem by modifying the session name:

server:
  servlet:
    session:
      cookie:
        name: OAUTH2CLIENTSESSION  # Solve the Possible CSRF detected - state parameter was required but no state could be found ed problem

Restart the project, test SSO, perfect solution!!!

_This paper introduces the code of SMS login development, which can access the security module in the code warehouse. The GitHub address of the project is https://github.com/BUG9/spring-security.

If you are interested in these, you are welcome to support star, follow, collect and forward!

Posted by Mesden on Wed, 11 Sep 2019 01:46:46 -0700