We mentioned

The usage and practices of Spring Security and JWT-BASED permission systems have been discussed in the previous article “Designing Permission Systems Based on Spring Security and JWT.” This article takes a closer look at multi-system single sign-on (SSO) and JWT permission control capabilities based on Spring Security Oauth2, which are still fairly common.

Code has been open source, at the end of the article, need to take


The theoretical knowledge

Some pre-knowledge needs to be learned and understood before this includes:

  • Spring SecurityBased on:SpringImplementation of theWebSystem authentication and authority module
  • OAuth2: one about authorization (authorization) open Web standards
  • Single sign-on (SSO) : In multiple applications, users only need to log in once to access all trusted applications
  • JWT: a basis for transferring information between network applicationsJSONOpen standards ((RFC 7519), used asJSONObject to transfer secure information between different systems. The main usage scenario is generally used to pass authenticated user identity information between the identity provider and the service provider

Goals to be accomplished

  • Goal 1: Design and implement a third party authorization Center service (Server) for user login, authentication, and permission processing
  • Goal 2: Can mount any number of client applications under the authorization Center (Client)
  • Goal 3: When a user accesses the secure page of a client application, the user is redirected to the authorization center for authentication. After authentication is complete, the user can access the services of the client application, and multiple client applications only need to log in once (” single sign-on “)SSO“).

Based on this goal driven, this paper designs three independent services, which are:

  • An Authorized Service Centre (codesheep-server)
  • Client Application 1 (codesheep-client1)
  • Client Application 2 (codesheep-client2)

Multi-module project construction

The three applications are organized through a multi-module Maven project, where the following dependencies need to be added to the project parent POM:

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> The < artifactId > spring - the boot - dependencies < / artifactId > < version > mid-atlantic moved. RELEASE < / version > <type>pom</type>
		<scope>import</scope>
	</dependency>

	<dependency>
		<groupId>io.spring.platform</groupId>
		<artifactId>platform-bom</artifactId>
		<version>Cairo-RELEASE</version>
		<type>pom</type>
		<scope>import</scope>
	</dependency>

	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-dependencies</artifactId>
		<version>Finchley.SR2</version>
		<type>pom</type>
		<scope>import</scope>
	</dependency>

</dependencies>
Copy the code

The project structure is as follows:


Establishment of the authorization and certification center

The authorization Center is essentially a Spring Boot application, so there are several major steps to complete:

  • pomAdd a dependency to
<dependencies>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-oauth2</artifactId>
	</dependency>
</dependencies>
Copy the code
  • projectymlConfiguration file:
server:
  port: 8085
  servlet:
    context-path: /uac
Copy the code

That is, the authorization center service is started on port 8085

  • Creates a mock user with the specified permissions
@Component
public class SheepUserDetailsService implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {

        if(!"codesheep".equals(s) )
            throw new UsernameNotFoundException("User" + s + "Doesn't exist." );

        return new User( s, passwordEncoder.encode("123456"), AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_NORMAL,ROLE_MEDIUM")); }}Copy the code

Create a mock user named codesheep with password 123456 and give normal (ROLE_NORMAL) and medium (ROLE_MEDIUM) permissions.

  • Authentication Server ConfigurationAuthorizationServerConfig
@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception {// Pass defining two client applications clients.inmemory ().withClient()"sheep1")
                .secret(new BCryptPasswordEncoder().encode("123456"))
                .authorizedGrantTypes("authorization_code"."refresh_token")
                .scopes("all")
                .autoApprove(false)
                .and()
                .withClient("sheep2")
                .secret(new BCryptPasswordEncoder().encode("123456"))
                .authorizedGrantTypes("authorization_code"."refresh_token")
                .scopes("all")
                .autoApprove(false);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        endpoints.tokenStore(jwtTokenStore()).accessTokenConverter(jwtAccessTokenConverter());
        DefaultTokenServices tokenServices = (DefaultTokenServices) endpoints.getDefaultAuthorizationServerTokenServices();
        tokenServices.setTokenStore(endpoints.getTokenStore());
        tokenServices.setSupportRefreshToken(true); tokenServices.setClientDetailsService(endpoints.getClientDetailsService()); tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer()); tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1)); Endpoints. TokenServices (tokenServices); } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.tokenKeyAccess("isAuthenticated()");
    }

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

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("testKey");
        returnconverter; }}Copy the code

The two most important things to do here are to define the passes for the two client applications (sheep1 and sheep2); The second is to configure the token as JWT token.

  • Spring Security Security configurationSpringSecurityConfig
@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Autowired
    private UserDetailsService userDetailsService;

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

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(userDetailsService);
        authenticationProvider.setPasswordEncoder(passwordEncoder());
        authenticationProvider.setHideUserNotFoundExceptions(false);
        return authenticationProvider;
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
                .requestMatchers().antMatchers("/oauth/**"."/login/**"."/logout/**")
                .and()
                .authorizeRequests()
                .antMatchers("/oauth/**").authenticated() .and() .formLogin().permitAll(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(authenticationProvider()); }}Copy the code

Client application creation and configuration

This article creates two client applications: Codesheep-client1 and codesheep-client2. Since they are similar, only one is used as an example

  • SSO client application configuration classClientWebsecurityConfigurer
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) @EnableOAuth2Sso public class ClientWebsecurityConfigurer extends WebSecurityConfigurerAdapter { @Override public void  configure(HttpSecurity http) throws Exception { http.antMatcher("/ * *").authorizeRequests() .anyRequest().authenticated(); }}Copy the code

All the complicated stuff is left to the notes!

  • Application. Yml configuration
auth-server: http://localhost:8085/uac
server:
  port: 8086

security:
  oauth2:
    client:
      client-id: sheep1
      client-secret: 123456
      user-authorization-uri: ${auth-server}/oauth/authorize
      access-token-uri: ${auth-server}/oauth/token
    resource:
      jwt:
        key-uri: ${auth-server}/oauth/token_key
Copy the code

The configuration here is very important, all need to communicate with the authorization center set up earlier

  • Creating a Test ControllerTestController
@RestController
public class TestController {

    @GetMapping("/normal")
    @PreAuthorize("hasAuthority('ROLE_NORMAL')")
    public String normal() {return "normal permission test success !!!";
    }

    @GetMapping("/medium")
    @PreAuthorize("hasAuthority('ROLE_MEDIUM')")
    public String medium() {
        return "medium permission test success !!!";
    }

    @GetMapping("/admin")
    @PreAuthorize("hasAuthority('ROLE_ADMIN')")
    public String admin() {
        return "admin permission test success !!!"; }}Copy the code

The test controller contains three interfaces and requires three permissions (ROLE_NORMAL, ROLE_MEDIUM, and ROLE_ADMIN) respectively. After the test, we will test the results one by one


Experimental verification

  • Start the authorization and Authentication Centercodesheep-server(Start locally8085Port)
  • Start the client applicationcodesheep-client1(Start locally8086Port)
  • Start the client applicationcodesheep-client2(Start locally8087Port)

First, use a browser to access the test interface of client1 (codesheep-client1) : localhost:8086/normal. Since user login authentication has not been performed, the login authentication page of the authorization center is automatically redirected: http://localhost:8085/uac/login:

Enter user name codesheep and password 123456 to log in and enter the authorization page:

After authorization is granted, the test interface of the previous client is automatically returned:

Codesheep-client1: localhost:8086/medium localhost:8086/medium

Due to the localhost: 8086 / normal and localhost: 8086 / permission to the requirements of the medium interface, user codesheep has, so to visit, then visit the interface: the higher authority localhost: 8086 / admin:

Client1 (codesheep-client1) : localhost:8087/normal

Client2 (codesheep-client2) interface can be accessed successfully after authorization:

This validates single sign-on SSO!


To be continued

Due to space constraints, this article should say that it practices streamlined processes such as SSO SSO and JWT permission control, but there are many other things that can be complex and specific, such as:

  • The clientclientCredentials and usersuserCan be managed uniformly with a database
  • certificationtokenYou can also use a database or cache for unified management
  • The unified login page of the Authorization Authority can be customized to look as required
  • The authorization page of a certification authority can also be customized or even removed
  • Including some exception prompts can also be customized

Anyway, go for it!

This article open source address is here: spring-boot-in-action, need to self-fetch


Write in the last

Due to the limited ability, if there is a mistake or improper place, please also criticize and correct, study together!

  • My Personal Blog: CodeSheep program sheep