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
- Interface address: http://localhost:20001/oauth/token
- Request method: POST
- Request parameters
- header configuration: content type = Application / JSON
- Client information
- Request result
Refresh token
- Interface address: http://localhost:20001/oauth/token
- Request method: POST
- Request parameters
- Client information configuration
- Request result
Obtain authorization code
- Link: http://localhost:20001/oauth/authorize?response_type=code&client_id=ams
- Login with user name and password
- Get authorization code: BfhrVd
Obtain the token according to the authorization code
- Interface address: http://localhost:20001/oauth/token
- Request method: POST
- Request parameters
- 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.