SpringCloud scaffold | 2.SpringCloud integrates security. Implements the certification center

The project is not open source for the time being. If you need source code, please pay attention to the author's official account "AI code division", and reply to the "SpringCloud scaffold" in the official account.

Create certification authority module

New module

Right click the parent project and click in sequence

Click next

Fill in the basic information and automatically fill in the parent module information

Click finish to complete the creation

Integrating spring security

Add dependency

Note that it is added in the pom of the auth module

Add spring cloud related dependencies

 <!--Spring Cloud & Alibaba -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>

        <!-- Registration Center -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>

        <!-- Configuration center -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

Add spring security related dependencies

  <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-jose</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- OAuth2 Authentication server-->
        <dependency>
            <groupId>org.springframework.security.oauth.boot</groupId>
            <artifactId>spring-security-oauth2-autoconfigure</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-jose</artifactId>
        </dependency>

Add startup class

/**
 * Created with IntelliJ IDEA.
 *
 * @author:  AI Coder
 * @date:  2021/11/24
 * @description: 
 * @modifiedBy: 
 * @version: 1.0
 */
@SpringBootApplication
@EnableDiscoveryClient
public class AuthApp {
    public static void main(String[] args) {
        SpringApplication.run(AuthApp.class, args);
    }
}

Introduction to basic components of spring security

ClientDetailsService

By implementing the service, it is used to add and obtain client logic

UserDetails

By implementing this class, it is used to encapsulate user information, or to expand user information

UserDetailsService

By implementing the service, it is used to add logic to obtain user information according to user name, which can be obtained from database or other services

AuthorizationServerConfigurerAdapter

This class is used to add some authorization service configurations, such as configuring client ClientDetailsService

WebSecurityConfigurerAdapter

This class is used to configure HttpSecurity
Relevant information, such as which resources need to be intercepted and certified, which resources need to be released, etc

DaoAuthenticationProvider

The default user name and password authorize the authentication provider

Introduction to main configuration

Create user encapsulation class

package com.ams.auth.security.details.user;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

/**
 * Created with IntelliJ IDEA.
 *
 * @author:  AI Coder
 * @date:  2021/11/24
 * @description: 
 * @modifiedBy: 
 * @version: 1.0
 */
/**
 * System management user authentication information
 *
 * @author <a href="mailto:xianrui0365@163.com">haoxianrui</a>
 * @date 2021/9/27
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SysUserDetails implements UserDetails {

    /**
     * Extended field
     */
    private Long userId;

    /**
     * Default field
     */
    private String username;
    private String password;
    private Boolean enabled;
    private Collection<SimpleGrantedAuthority> authorities;


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public String getUsername() {
        return this.username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return this.enabled;
    }
}

Create a service that obtains encapsulated user information according to the user name

At present, only the user information is written. Later, the user information will be obtained from the management side service through feign. For the time being, it is only for the service to run through

package com.ams.auth.security.details.user;

import com.ams.auth.comm.enums.PasswordEncoderTypeEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collection;
/**
 * Created with IntelliJ IDEA.
 *
 * @author:  AI Coder
 * @date:  2021/11/24
 * @description: 
 * @modifiedBy: 
 * @version: 1.0
 */
@Service("sysUserDetailsService")
@Slf4j
@RequiredArgsConstructor
public class SysUserDetailsServiceImpl implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // Later, the user information is obtained from the management end
        SysUserDetails userDetails = loadUser(username);
        if (!userDetails.isEnabled()) {
            throw new DisabledException("The account has been disabled!");
        } else if (!userDetails.isAccountNonLocked()) {
            throw new LockedException("The account has been locked!");
        } else if (!userDetails.isAccountNonExpired()) {
            throw new AccountExpiredException("This account has expired!");
        }
        return userDetails;
    }

    private SysUserDetails loadUser(String username) {
        Collection<SimpleGrantedAuthority> authorities =new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("admin"));
        return SysUserDetails.builder()
                .userId(1L)
                .username(username)
                .enabled(true)
                .authorities(authorities)
                .password(PasswordEncoderTypeEnum.BCRYPT.getPrefix() + new BCryptPasswordEncoder().encode("123456789")).build();
    }

}

Create client information acquisition service

package com.ams.auth.security.details.client;

import com.ams.auth.comm.enums.PasswordEncoderTypeEnum;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.stereotype.Service;

/**
 * Created with IntelliJ IDEA.
 *
 * @author:  AI Coder
 * @date:  2021/11/24
 * @description: 
 * @modifiedBy: 
 * @version: 1.0
 */
@Service
@RequiredArgsConstructor
public class ClientDetailsServiceImpl implements ClientDetailsService {
    @Override
    @Cacheable(cacheNames = "auth", key = "'oauth-client:'+#clientId")
    public ClientDetails loadClientByClientId(String clientId) {
        // Later, it is obtained from the management side through feign. At present, it is written dead
        BaseClientDetails clientDetails = new BaseClientDetails(
                "ams",
                "",
                "all",
                "password,client_credentials,refresh_token,authorization_code",
                "",
                "http://www.baidu.com"

        );
        clientDetails.setClientSecret(PasswordEncoderTypeEnum.NOOP.getPrefix() + "ams");
        clientDetails.setAccessTokenValiditySeconds(3600);
        clientDetails.setRefreshTokenValiditySeconds(36000000);
        return clientDetails;
    }
}

Create security configuration

The following contents are configured here:

  • Set which resources are not blocked
  • Set basic authentication
  • Add the default user name and password authenticator provider DaoAuthenticationProvider
  • Set the user in the user name password authentication provider to get the source sysUserDetailsService
package com.ams.auth.security.config;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * Created with IntelliJ IDEA.
 *
 * @author:  AI Coder
 * @date:  2021/11/24
 * @description: 
 * @modifiedBy: 
 * @version: 1.0
 */
@Configuration
@EnableWebSecurity
@Slf4j
@RequiredArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final UserDetailsService sysUserDetailsService;


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

    /**
     * Authentication management object
     *
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * Add custom authenticator
     *
     * @param auth
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(daoAuthenticationProvider());
    }

    /**
     * Set the default user name password authentication authorization provider
     *
     * @return
     */
    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(sysUserDetailsService);
        provider.setPasswordEncoder(passwordEncoder());
        provider.setHideUserNotFoundExceptions(false); // Whether to hide the user. There is no exception. Default: true - hide; false - throw an exception;
        return provider;
    }

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


}

Add authorization service related configuration

The following contents are configured here:

  • Set oauth client to get information from clientDetailsService
  • Set the default token storage method (later changed to redis storage)
  • Add token enhancer (add user information in token)
  • Add token encryption method
package com.ams.auth.security.config;

import cn.hutool.core.collection.CollectionUtil;
import com.ams.auth.security.details.client.ClientDetailsServiceImpl;
import com.ams.auth.security.details.user.SysUserDetails;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;

import java.security.KeyPair;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * Created with IntelliJ IDEA.
 *
 * @author:  AI Coder
 * @date:  2021/11/24
 * @description: 
 * @modifiedBy: 
 * @version: 1.0
 */
@Configuration
@EnableAuthorizationServer
@RequiredArgsConstructor
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    private final AuthenticationManager authenticationManager;
    private final ClientDetailsServiceImpl clientDetailsService;

    /**
     * OAuth2 client
     */
    @Override
    @SneakyThrows
    public void configure(ClientDetailsServiceConfigurer clients) {
        clients.withClientDetails(clientDetailsService);
    }

    /**
     * Configure authorization, access endpoint of token and token services
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        // Token enhancement
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> tokenEnhancers = new ArrayList<>();
        tokenEnhancers.add(tokenEnhancer());
        tokenEnhancers.add(jwtAccessTokenConverter());
        tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);

        // Obtain the authorizer of the original default authorization mode (authorization code mode, password mode, client mode and simplified mode)
        List<TokenGranter> granterList = new ArrayList<>(Arrays.asList(endpoints.getTokenGranter()));

        CompositeTokenGranter compositeTokenGranter = new CompositeTokenGranter(granterList);
        endpoints
                .authenticationManager(authenticationManager)
                .accessTokenConverter(jwtAccessTokenConverter())
                .tokenEnhancer(tokenEnhancerChain)
                .tokenGranter(compositeTokenGranter)
                .reuseRefreshTokens(true)
                .tokenServices(tokenServices(endpoints))
        ;
    }

    public DefaultTokenServices tokenServices(AuthorizationServerEndpointsConfigurer endpoints) {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> tokenEnhancers = new ArrayList<>();
        tokenEnhancers.add(tokenEnhancer());
        tokenEnhancers.add(jwtAccessTokenConverter());
        tokenEnhancerChain.setTokenEnhancers(tokenEnhancers);

        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(endpoints.getTokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setClientDetailsService(clientDetailsService);
        tokenServices.setTokenEnhancer(tokenEnhancerChain);
        return tokenServices;

    }

    /**
     * JWT Content enhancement
     */
    @Bean
    public TokenEnhancer tokenEnhancer() {
        return (accessToken, authentication) -> {
            Map<String, Object> additionalInfo = CollectionUtil.newHashMap();
            Object principal = authentication.getUserAuthentication().getPrincipal();
            if (principal instanceof SysUserDetails){
                SysUserDetails sysUserDetails = (SysUserDetails) principal;
                additionalInfo.put("userId", sysUserDetails.getUserId());
                additionalInfo.put("username", sysUserDetails.getUsername());
                ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
            }

            return accessToken;
        };
    }

    /**
     * Sign the token using asymmetric encryption algorithm
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setKeyPair(keyPair());
        return converter;
    }

    /**
     * Get the key pair from the keystore (public key + private key)
     */
    @Bean
    public KeyPair keyPair() {
        KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());
        KeyPair keyPair = factory.getKeyPair("jwt", "123456".toCharArray());
        return keyPair;
    }
}

Generate jks file

Generate using the java command line

keytool -genkey -alias jwt -keyalg RSA -keysize 1024 -keystore jwt.jks -validity 365

Follow the prompts

Copy jks to the resource directory of the project

Create and get token entry

In order to catch all exceptions in the authentication process, this is achieved by copying the token entry in security
In fact, there is little content, which is triggered by manually calling the method of tokenPoint

package com.ams.auth.security;

import cn.hutool.json.JSONUtil;
import com.ams.auth.comm.RequestUtils;
import com.ams.auth.comm.result.R;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.security.Principal;
import java.util.Map;
/**
 * Created with IntelliJ IDEA.
 *
 * @author:  AI Coder
 * @date:  2021/11/24
 * @description: 
 * @modifiedBy: 
 * @version: 1.0
 */
@RestController
@RequestMapping("/oauth")
@AllArgsConstructor
@Slf4j
public class AuthController {

    private final TokenEndpoint tokenEndpoint;

    @PostMapping("/token")
    public Object postAccessToken(
            Principal principal,
            @RequestParam Map<String, String> parameters
    ) throws HttpRequestMethodNotSupportedException {
        OAuth2AccessToken accessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
        return R.ok(accessToken);
    }
}

Add exception handling class

package com.ams.auth.comm.exception;

import com.ams.auth.comm.result.R;
import com.ams.auth.comm.result.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * Created with IntelliJ IDEA.
 *
 * @author:  AI Coder
 * @date:  2021/11/24
 * @description: 
 * @modifiedBy: 
 * @version: 1.0
 */
@RestControllerAdvice
@Slf4j
@Order(-1)
public class AuthExceptionHandler {

    /**
     * user does not exist
     *
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(UsernameNotFoundException.class)
    public R handleUsernameNotFoundException(UsernameNotFoundException e) {
        return R.failed(ResultCode.USER_NOT_EXIST);
    }

    /**
     * Abnormal user name and password
     *
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(InvalidGrantException.class)
    public R handleInvalidGrantException(InvalidGrantException e) {
        return R.failed(ResultCode.USERNAME_OR_PASSWORD_ERROR);
    }

    /**
     * Abnormal user name and password
     *
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(InvalidClientException.class)
    public R handleInvalidGrantException(InvalidClientException e) {
        return R.failed(ResultCode.CLIENT_AUTHENTICATION_FAILED);
    }


    /**
     * Account exception (disabled, locked, expired)
     *
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({InternalAuthenticationServiceException.class})
    public R handleInternalAuthenticationServiceException(InternalAuthenticationServiceException e) {
        return R.failed(e.getMessage());
    }

    /**
     * token Invalid or expired
     *
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({InvalidTokenException.class})
    public R handleInvalidTokenExceptionException(InvalidTokenException e) {
        return R.failed(e.getMessage());
    }

}

Add bootrap.yml

Create the bootstrap.yml file under AMS auth - > resources and configure the nacos client information
Cloud. Lebao. Site: 8848 - > change to your own nacos address

server:
  port: 20001
spring:
  application:
    name: ams-auth
  cloud:
    nacos:
      # Registration Center
      discovery:
        server-addr: http://cloud.lebao.site:8848
      # Configuration center
      config:
        server-addr: ${spring.cloud.nacos.discovery.server-addr}
        file-extension: yaml
        shared-configs[0]:
          data-id: ams-common.yaml
          refresh: true
logging:
  level:
    spring.: DEBUG

Add configuration in nacos

  • ams-auth.yaml
TEST: 111 # It is configured casually. It needs to be configured as needed later
  • ams-common.yaml
redis:
  host: cloud.lebao.site
  port: 6379
  password: root
mysql:
  host: cloud.lebao.site
  port: 3306
  username: root
  password: root

![image.png](https://img-blog.csdnimg.cn/img_convert/92cb44f36e08b2f19d6e99fb07f37c23.png#clientId=u71937da6-a7c7-4&from=paste&height=111&id=u7a387e5a&margin=[object Object]&name=image.png&originHeight=222&originWidth=2670&originalType=binary&ratio=1&size=45555&status=done&style=none&taskId=u52987b45-beff-47e3-9acd-616351bd035&width=1335)

Test get token

User name and password acquisition

  • header configuration: content type = Application / JSON
  • Client information

  • Request result

Refresh token

  • Client information configuration

  • Request result

Obtain authorization code

  • Get authorization code: BfhrVd

Obtain the token according to the authorization code

  • Return token

summary

This article introduces how to integrate spring security, the core components of spring security and what they are used for, but here we just use its token acquisition function. In the next issue, we will integrate the gateway to implement a complete authentication system.

welfare


Pay attention to the official account "AI code division" to collect 2021 latest interview materials and a set of latest micro service course.

Keywords: Java Spring Boot Back-end Spring Cloud

Added by benreisner on Wed, 01 Dec 2021 08:28:33 +0200