The last article focused on the fundamentals of Spring Security. This article focuses on how to configure and use Spring Security, including

  1. OAuth2.0 authentication configuration
  2. Custom login page implementation and configuration
  3. JWT Token generation configuration

Basic introduction

Pom reference

Spring Security and Spring Security oAuth2 dependencies need to be added

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
Copy the code

OAuth2.0 authentication configuration

WebSecurityConfig configuration

  1. Configure the login page first
  • Set the customized login page to index.html and the back-end user name and password to “/login”.
  • Because for a custom login page, the configuration “/index.html”, “/login”, “/resources/**”, “/static/” does not require authentication, other requests require authentication. Otherwise, the login page cannot be opened
  • You can delete the session and cookie when logging out, and customize the handler after logging out successfully
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity HTTP) throws Exception {// Default support./login Implement authorization_code authentication http.formlogin ().loginPage()"/index.html").loginProcessingUrl("/login")
            .and()
            .authorizeRequests()
            .antMatchers("/index.html"."/login"."/resources/**"."/static/**".permitAll().anyRequest() // Any request.authenticated ()// requires authentication.and ().logout().invalidateHttpSession()true).deleteCookies("JSESSIONID").logoutSuccessHandler(customLogoutSuccessHandler).permitAll()
            .and()
            .csrf().disable();
    }
Copy the code
  1. Add customUsernamePasswordAuthenticationProviderIs used for user name and password authentication
@ Override public void the configure (AuthenticationManagerBuilder auth) {/ / custom authentication provider for user name and password authentication auth.authenticationProvider(new UsernamePasswordAuthenticationProvider(usernamePasswordUserDetailService)); } @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
Copy the code
  1. implementationUsernamePasswordAuthenticationProviderImplement the Authenticate and SUPPORTS methods for user authentication
Public class UsernamePasswordAuthenticationProvider implements AuthenticationProvider {/ / custom implementations of user detail, Use the user name and password authentication, user information access to private UsernamePasswordUserDetailService userDetailService; public UsernamePasswordAuthenticationProvider(UsernamePasswordUserDetailService userDetailService) { this.userDetailService = userDetailService; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String username = authentication.getName(); String password = authentication.getCredentials().toString(); // Verify the user name and passwordif (userDetailService.verifyCredential(username, password)) {

            List<GrantedAuthority> authorities = new ArrayList<>();
            authorities.add(new SimpleGrantedAuthority("user")); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password, authorities); / / get the user information token. SetDetails (userDetailService. GetUserDetail (username));return token;
        }
        returnnull; } @Override public boolean supports(Class<? > aClass) {// Use this provider for authentication when logging in to the user name and passwordreturn(UsernamePasswordAuthenticationToken.class.isAssignableFrom(aClass)); }}Copy the code
  1. Compile the customized login page and copy it to the resource/public file. The login page is index. HTML. You can create your own version of the login page, or see the login-UI source link in awesome-admin at the end of this article.

JWT Token configuration

Oauth2AuthorizationConfig configuration

  1. Configure Oauth2 Client. This article implements Oauth2 Client through ClientDetailsService.
@Configuration @EnableAuthorizationServer public class Oauth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter { @Autowired ClientDetailsService clientDetailsService; . @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.withClientDetails(clientDetailsService); }}Copy the code

ClientDetailsService inherits ClientDetailsService. Normally, it should be read from the database. For the convenience of demonstration, this article directly sets it in the code. Client information includes appId, appSecret, grantTypes, accessExpireTime, refreshExpireTime, redirectUrl, scopes and other OAuth2 protocol Client information.

@Service @Primary public class ClientDetailsServiceImpl implements ClientDetailsService { @Override public ClientDetails  loadClientByClientId(String s) {if ("weixin".equals(s) || "app".equals(s) || "mini_app".equals(s) || "global".equals(s)) {
            AppCredential credential = new AppCredential();
            credential.setAppId(s);
            credential.setAppSecret("testpassword");
            credential.setGrantTypes("authorization_code,client_credentials,password,refresh_token,mini_app");
            credential.setAccessExpireTime(3600 * 24);
            credential.setRefreshExpireTime(3600 * 24 * 30);
            credential.setRedirectUrl("http://localhost:3006,http://localhost:3006/auth,http://localhost:8000,http://localhost:8000/auth");
            credential.setScopes("all");
            return new AppCredentialDetail(credential);
        }
        returnnull; }}Copy the code
  1. Configure the JWT Token service to generate tokens. Configuration content, production environment has never changed, recommended direct copy
@Configuration
@EnableAuthorizationServer
public class Oauth2AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
    ...
    @Autowired
    TokenServiceFactory tokenServiceFactory;

    @Autowired
    @Qualifier("authenticationManagerBean") private AuthenticationManager authenticationManager; @Override public void configure(final AuthorizationServerEndpointsConfigurer endpoints) { endpoints .tokenServices(tokenServiceFactory.JwtTokenService()) .authenticationManager(authenticationManager); }}Copy the code

In this paper, TokenServiceFactory is implemented to implement TokenService for Token generation. This part of the code is relatively trivial, so it is recommended to directly use copy&Paste technology (because the production has been used for a year, it has not been touched). Three key points:

  1. Implement TokenEnhancer to add some custom information to the token
  2. Implement TokenConverter for which fields to put into token
  3. Set the public and key generated by the token in the accessTokenConverter method
@Service
public class TokenServiceFactory {

    private TokenKeyConfig tokenKeyConfig;
    private ClientDetailsService clientDetailsService;

    @Autowired
    public TokenServiceFactory(
            TokenKeyConfig tokenKeyConfig,
            ClientDetailsService clientDetailsService) {
        this.tokenKeyConfig = tokenKeyConfig;
        this.clientDetailsService = clientDetailsService;
    }

    @Bean
    @Primary
    public AuthorizationServerTokenServices JwtTokenService() { final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); // Set custom TokenEnhancer, TokenConverter, The content of the increase in token used in a custom tokenEnhancerChain. SetTokenEnhancers (arrays.aslist (tokenEnhancer (), accessTokenConverter ()));return defaultTokenService(tokenEnhancerChain);
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() { final JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setAccessTokenConverter(new CustomAccessTokenConverter()); // Set the public key and key generated by token. The key is stored in the resource directory. Final KeyStoreKeyFactory KeyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource(tokenKeyConfig.getPath()), tokenKeyConfig.getPassword().toCharArray()); converter.setKeyPair(keyStoreKeyFactory.getKeyPair(tokenKeyConfig.getAlias()));return converter;
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public org.springframework.security.oauth2.provider.token.TokenEnhancer tokenEnhancer() {
        return new TokenEnhancer();
    }

    private AuthorizationServerTokenServices defaultTokenService(TokenEnhancerChain tokenEnhancerChain) {
        final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        defaultTokenServices.setTokenEnhancer(tokenEnhancerChain);
        defaultTokenServices.setClientDetailsService(clientDetailsService);
        returndefaultTokenServices; }}Copy the code

Key generation reference: www.jianshu.com/p/c9d5a2aa8… TokenEnhancer is used to add custom content to the token. It usually needs to be implemented on its own, often changing as needs change

public class TokenEnhancer implements org.springframework.security.oauth2.provider.token.TokenEnhancer { @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { final Map<String, Object> additionalInfo = new HashMap<>(2); Authentication userAuthentication = authentication.getUserAuthentication(); // If the authentication is client, the authentication is usually between services. The admin role is added to the tokenif (authentication.isClientOnly()) {
            List<String> authorities = new ArrayList<>(1);
            authorities.add("admin");
            additionalInfo.put("authorities", authorities);

        } else{// If user authentication is used, add user detail to token, set user detail when authentication user name and password additionalinfo.put ("userInfo", userAuthentication.getDetails()); // Convert authorities to string, Facilitate the json serialization Set < GrantedAuthority > rolesInfo = new HashSet < > (userAuthentication. GetAuthorities ()); additionalInfo.put("authorities", rolesInfo.stream().map(auth -> auth.getAuthority()).toArray());
        }

        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
        returnaccessToken; }}Copy the code

CustomAccessTokenConverter usually put all claims in the token

@Component public class CustomAccessTokenConverter extends DefaultAccessTokenConverter { @Override public OAuth2Authentication extractAuthentication(Map<String, ? > claims) { OAuth2Authentication authentication = super.extractAuthentication(claims); authentication.setDetails(claims);returnauthentication; }}Copy the code

ResourceServerConfig configuration

In addition to being an authentication server, Spring Security is itself a resource server. In this project, Spring Security supports custom login pages and some APIS, so you need to configure ResourceServerConfig

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {

        http.requestMatchers().antMatchers("/api/**"."/users/**"."/static/**")
                .and()
                .authorizeRequests()
                .antMatchers("/api/**"."/users/**").authenticated(); }}Copy the code

The Application note

@ SpringBootApplication @ EnableResourceServer / / as the resource server @ EnableGlobalMethodSecurity (prePostEnabled =true// Allow comments to precede methods @preauthorize ("hasAnyAuthority('admin',)") public class AuthApplication { public static void main(String[] args) { SpringApplication.run(AuthApplication.class, args); }}Copy the code

The effect

Enter the login address. Because it involves the coordination between the front and back ends and the conversion of exchanging token through code, this project realized a front-end project to directly use Spring Security and its login page. After successful login, jump back to the front page and refer to the source code admin-UI at the end of the article.

  1. On the login page, enter localhost:8000 and then jump to the login page of auth service as shown in the following figure. The user name can be admin/admin and you can actually enter any password because the authentication code above does not check the password

  2. JWT Token is saved in cookie after successful login page.

To directly access the login page in Spring Security, start your Spring Security service to access the following connection (assuming service port 5000), You can also download the admin-service source code of awesome-admin to start config, Registry, auth services. After login, the redirect_URI can be successfully redirected and a code is generated with which the token can be obtained through Postman.

  1. Use the following login url, enter the user name and password are admin redirect_uri http://localhost:5000/auth/oauth/authorize? after the jump response_type=code&client_id=app&redirect_uri=http://localhost:3006/auth

  2. After the jump, you can see the code in the connection

  3. Obtain the token through API with code as parameter. Note that basic Auth authentication is used. Before a user name and password is your ClientDetailsService configured in the app and appsecret http://localhost:5000/auth/oauth/token? grant_type=authorization_code&code=pYIspF&redirect_uri=http://localhost:3006/auth

Program for Copy&Paste

  1. Awesome – admin source gitee.com/awesome-eng…