当前位置:网站首页>Analyze the implementation process of oauth2 distributed authentication and authorization based on the source code
Analyze the implementation process of oauth2 distributed authentication and authorization based on the source code
2022-06-24 22:44:00 【Zhang Zixing's blog】
Preface
I was responsible for logging in 、 register 、 Authentication related tasks , Because it is a distributed project, it is a little complicated to code , Some difficulties have been encountered , own debug A related source code , This is to record your feelings and experiences .
Oauth2 Introduction to password Mode authentication
Oauth2 Two commonly used authorization modes , firstly : Password mode , second : Authorization code mode . Maybe many people are not familiar with these two models , The following is a brief introduction to .
Authorization code mode : Wechat scanning code login is a good example , Third party applications want to develop a set of login system efficiently and quickly , But I don't want to spend too much time to develop my own Certification Center , Wechat is used by many users app, The users in it are all real users , So wechat is used Oauth2 Provide an access point for these third-party applications , Any user who can scan the wechat code to log in is a real user , After the user successfully logs in and authorizes ( On wechat app Scan the internal code to request the wechat authentication center ), Wechat knows that this third-party website is a website trusted by the user , Then the third-party application is given certain operation permission to access and obtain some user data . For details, please refer to Login analysis through wechat scanning code oauth2 Certification and authorization Technology ( In short The authentication center and the client are not developed by the same company to use the authorization code mode )
Password mode : The wechat client uses the wechat account and is authorized by the wechat authentication center , Can log in normally . The client of the third-party application uses the third-party account and is authorized by the wechat authentication center , Will not be able to log in .( In short The authentication center and the client are developed by the same company using the password mode )
Oauth2 Basic configuration overview
Let's start with a brief statement oauth2 So let's configure this , Are some configuration code , If readers are in a hurry to integrate , direct copy Just add it to your own project , These configurations will be analyzed and introduced in detail below . Of course, it is better to read this article in combination with the comments in the code .
@Configuration
@EnableAuthorizationServer
public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenEnhancer jwtTokenEnhancer;
@Autowired
private StringRedisTemplate stringRedisTemplate;
private TokenGranter tokenGranter;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("client-zzh")
.secret(passwordEncoder.encode("123456"))
.scopes("all")
// Configure supported authentication modes
.authorizedGrantTypes("password", "captcha", "refresh_token")
// To configure token Failure time
.accessTokenValiditySeconds(60 * 2).refreshTokenValiditySeconds(3600 * 4);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> delegates = new ArrayList<>();
delegates.add(jwtTokenEnhancer);
delegates.add(accessTokenConverter());
enhancerChain.setTokenEnhancers(delegates); // To configure JWT Content enhancer for
endpoints.authenticationManager(authenticationManager).userDetailsService(userDetailsService) // Configure the service that loads user information
.accessTokenConverter(accessTokenConverter()).tokenEnhancer(enhancerChain).tokenGranter(tokenGranter(endpoints)).reuseRefreshTokens(false);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients();
// Turn on check_token Access to the interface
security.checkTokenAccess("permitAll()");
}
/** * To configure jwt converter */
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setKeyPair(keyPair());
return jwtAccessTokenConverter;
}
@Bean
public KeyPair keyPair() {
// from classpath Get the secret key pair from the certificate under
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());
return keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());
}
/** * Customize provider Realization */
@Bean
public AuthenticationProvider customAuthenticationProvider() {
CustomAuthenticationProvider customAuthenticationProvider = new CustomAuthenticationProvider();
customAuthenticationProvider.setUserDetailsService(userDetailsService);
customAuthenticationProvider.setHideUserNotFoundExceptions(false);
customAuthenticationProvider.setPasswordEncoder(passwordEncoder);
return customAuthenticationProvider;
}
public TokenGranter tokenGranter(AuthorizationServerEndpointsConfigurer endpoints) {
if (tokenGranter == null) {
tokenGranter = new TokenGranter() {
private CompositeTokenGranter delegate;
/** * utilize delegate( Delegate pattern ) Override authentication methods * @param grantType * @param tokenRequest * @return */
@Override
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
if (delegate == null) {
delegate = new CompositeTokenGranter(getDefaultTokenGranters(endpoints));
}
return delegate.grant(grantType, tokenRequest);
}
};
}
return tokenGranter;
}
private List<TokenGranter> getDefaultTokenGranters(AuthorizationServerEndpointsConfigurer endpoints) {
AuthorizationServerTokenServices tokenServices = endpoints.getTokenServices();
AuthorizationCodeServices authorizationCodeServices = endpoints.getAuthorizationCodeServices();
OAuth2RequestFactory requestFactory = endpoints.getOAuth2RequestFactory();
ClientDetailsService clientDetailsService = endpoints.getClientDetailsService();
List<TokenGranter> tokenGranters = new ArrayList();
/** * add to oauth2 The four authorization modes come with it . And our custom authorization mode , You can extend it by injecting different arguments based on the requirements , Here I inject a RedisTemplate */
tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetailsService, requestFactory));
tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetailsService, requestFactory));
tokenGranters.add(new ImplicitTokenGranter(tokenServices, clientDetailsService, requestFactory));
tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetailsService, requestFactory));
/** * Add our custom authorization mode , You can extend it by injecting different arguments based on the requirements , Here I inject a RedisTemplate */
tokenGranters.add(new CaptchaTokenGranter(tokenServices, clientDetailsService, requestFactory, authenticationManager, stringRedisTemplate));
if (authenticationManager != null) {
// Add password mode
tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));
}
return tokenGranters;
}
}
Spring Security To configure
This is mainly for some static resources 、Http Request a white list release configuration , Readers can make corresponding configurations according to their own needs .
/** * SpringSecurity To configure */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
}
/** * Configure some web White list visit Lu Jin */
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
"/v2/api-docs",
"/swagger-resources/configuration/ui",
"/swagger-resources",
"/swagger-resources/configuration/security",
"/swagger-ui.html",
"/css/**",
"/js/**",
"/images/**",
"/webjars/**",
"**/favicon.ico",
"/index"
);
}
/** * To configure http White list of requests */
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
.antMatchers("/user/getUserInfo").permitAll()
.antMatchers("/oauth/token").permitAll()
.antMatchers("/rsa/publicKey").permitAll()
.antMatchers("/oauth/login").permitAll()
.antMatchers("/oauth/verifyCharge").permitAll()
.antMatchers("/oauth/logout").permitAll()
.antMatchers("/oauth/isOnLine").permitAll()
.antMatchers("/oauth/loginConfirm").permitAll()
.antMatchers("/user/rsa/pubKey").permitAll()
.antMatchers("/login_url").permitAll()
.anyRequest().authenticated()
.and().csrf().disable();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
JWT Introduction to intensifier
In the configuration Oauth2 Medium AuthorizationServerEndpointsConfigurer When , We can JwtTokenEnhancer Configuration in ,JwtTokenEnhancer Its function is to provide us with an extension point , For those that have already been generated Jwt in , Add some additional customized parameters .
/** * JWT Content enhancer : Custom add additional parameters to plug in JWT in */
@Component
public class JwtTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
Map<String, Object> info = new HashMap<>();
// Put the user userName Set to JWT in
info.put("userName", securityUser.getUsername());
info.put("roles", securityUser.getAuthorities());
System.err.println(info);
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
return accessToken;
}
}
Rewriting acquisition Token The interface of
Write a simple Controller, The writing path is /oauth/token An interface of , The purpose is to cover the one that the framework helped me write /oauth/token Interface .
@Api(tags = " authentication center ")
@Slf4j
@RequestMapping("/oauth")
@RestController
@RefreshScope
public class AuthController {
@Autowired
private TokenEndpoint tokenEndpoint;
@Autowired
private RedisTemplate redisTemplate;
@ApiOperation(value = " obtain token", notes = "")
@PostMapping("/token")
public Result getToken(@RequestParam Map<String, String> parameters, Principal principal) throws Exception {
String username = parameters.get("username");
parameters.put("password", ""); //require
OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
Oauth2TokenDto oauth2TokenDto = Oauth2TokenDto.builder()
.token(oAuth2AccessToken.getValue())
.refreshToken(oAuth2AccessToken.getRefreshToken().getValue())
.expiresIn(oAuth2AccessToken.getExpiresIn())
.tokenHead("Bearer ").build();
JWSObject jwsObject = JWSObject.parse(oAuth2AccessToken.getValue());
String payload = jwsObject.getPayload().toString();
JSONObject jsonObject = JSONUtil.parseObj(payload);
String jti = jsonObject.getStr(AuthConstants.JWT_JTI);
redisTemplate.opsForValue().set(RedisConstants.USER_LOGIN_JTI + jti, username);
log.info("Response-> url =/token,Parameters:{" + parameters + "},Result:{" + oauth2TokenDto + "}");
return Result.success(oauth2TokenDto);
}
}
If you do not override /oauth/token Interface , By default, follow the code in the figure below , Directly in this.postAccessToken(principal, parameters) Issue generation at token. Similarly, we rewrite this method , Its award token No changes have been made to the core code of , Also through injection TokenEndpoint , Call the postAccessToken Method generation token , We just made some business logic extensions before and after . Readers expand according to their own needs
Well, the general preparations are almost done , Now let's start to customize some functions in depth .
In depth analysis postAccessToken
You can see the issue token That is, the code verifies the parameters of the request ,
Then down debug We will find that we have come to the following figure , Parameter verification passed , Then start the authorization operation . Don't worry. Let's go on .
Take a closer look at this this.getTokenGranter() Method , Does it look familiar , Yes, this TokenGranter Above Oauth2 Basic configuration overview It has been configured in the section . At the beginning, some small partners may think the configuration inside is too abstruse , It's not , Reading the following text carefully will make you more enlightened .
Oauth2 Authorization part of the source code analysis
As the article has been configured at the beginning TokenGranter This Bean And rewritten grant() Method , as follows . It is found that there is a nested one called CompositeTokenGranter Things that are , Authorization is all about leaving CompositeTokenGranter Medium grant() Method , Then we began to study CompositeTokenGranter .
CompositeTokenGranter( Delegate pattern , The essence of source code design !!)
When I first saw this class , I feel that this code writes something , There's a... In it list
Collection is used to temporarily store all of our TokenGrant , Traverse the corresponding grant Method .
By now we have known Oauth2 It's time to delegate , Can we also expand by ourselves TokenGrant , Implement an authorization mode by yourself ? Observe Oauth2 The code in the basic configuration overview section , Can not help but sigh , It turns out that the thing we were equipped with came into effect here .
Custom authentication code authorization mode ( The extension point )
By analyzing the source code , We directly copy a copy of the source code in the password mode , Slight improvement , Add the business logic of your own verification code , And then Cranter Weave into AuthorizationServerEndpointsConfigurer And it will take effect . Don't ask me why I weave into AuthorizationServerEndpointsConfigurer in , Instead of weaving into other places , because AuthorizationServerEndpointsConfigurer.tokenGranter(tokenGranter(endpoints)) You know what I mean . Interested readers can study the source code inside . After reading the following code , combination Oauth2 Basic configuration overview Section Know why CaptchaTokenGranter Will take effect and when it will take effect Just go .
public class CaptchaTokenGranter extends AbstractTokenGranter {
/** * Custom authentication code authorization mode */
private static final String GRANT_TYPE = "captcha";
private final AuthenticationManager authenticationManager;
private StringRedisTemplate redisTemplate;
public CaptchaTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, AuthenticationManager authenticationManager, StringRedisTemplate redisTemplate
) {
super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
this.authenticationManager = authenticationManager;
this.redisTemplate = redisTemplate;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = new LinkedHashMap(tokenRequest.getRequestParameters());
String captcha = parameters.get("captcha");
// Verification code is not 1, Prompt verification code failed
if (!"1".equals(captcha) || StringUtils.isEmpty(captcha)) throw new InvalidGrantException(" Verification code error ");
String username = parameters.get("username");
String password = parameters.get("password");
// The same logic as password mode
Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
try {
userAuth = this.authenticationManager.authenticate(userAuth);
} catch (AccountStatusException var8) {
throw new InvalidGrantException(var8.getMessage());
} catch (BadCredentialsException var9) {
throw new InvalidGrantException(var9.getMessage());
}
if (userAuth != null && userAuth.isAuthenticated()) {
OAuth2Request storedOAuth2Request = this.getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
} else {
throw new InvalidGrantException("Could not authenticate user: " + username);
}
}
}
to grant authorization -> authentication (ProviderManager)
After our custom Grant() After the process , Will follow the trend ProviderManager Medium authenticate() Method , Start certification
The authentication process ( There are extension points )
Traverse all Providers, Let's see if there is a suitable one Provider You can use , without , Again from praent Trying to get Privider Perform subsequent authentication operations . Little book remember , This praent Is an extensible point .
Since I have extended the authentication logic myself, it will eventually be implemented to CustomAuthenticationProvider Medium retrieveUser() In the method .
Expand retrieveUser Method ()
I don't know if my friends in front of the screen , By UserDetails Of loadUserByUsername Methods can only be passed username I have been puzzled by my troubles , I tm Our company wants to do multi-user login , One username Not enough , Ha ha ha ha ha ha ha , Don't worry , We have now expanded , You pass 100 Parameters are our loadUserByUsername The method is acceptable .
public class CustomAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
private PasswordEncoder passwordEncoder;
private volatile String userNotFoundEncodedPassword;
private CustomUserDetailsService userDetailsService;
private UserDetailsPasswordService userDetailsPasswordService;
public CustomAuthenticationProvider() {
this.setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
} else {
String presentedPassword = authentication.getCredentials().toString();
if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
this.logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
}
protected void doAfterPropertiesSet() {
Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
}
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
this.prepareTimingAttackProtection();
Map<String,String> map = (Map<String, String>) authentication.getDetails(); // Custom add
try {
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(map);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
} else {
return loadedUser;
}
} catch (UsernameNotFoundException var4) {
this.mitigateAgainstTimingAttack(authentication);
throw var4;
} catch (InternalAuthenticationServiceException var5) {
throw var5;
} catch (Exception var6) {
throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
}
}
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
boolean upgradeEncoding = this.userDetailsPasswordService != null && this.passwordEncoder.upgradeEncoding(user.getPassword());
if (upgradeEncoding) {
String presentedPassword = authentication.getCredentials().toString();
String newPassword = this.passwordEncoder.encode(presentedPassword);
user = this.userDetailsPasswordService.updatePassword(user, newPassword);
}
return super.createSuccessAuthentication(principal, authentication, user);
}
private void prepareTimingAttackProtection() {
if (this.userNotFoundEncodedPassword == null) {
this.userNotFoundEncodedPassword = this.passwordEncoder.encode("userNotFoundPassword");
}
}
private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
}
}
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
this.passwordEncoder = passwordEncoder;
this.userNotFoundEncodedPassword = null;
}
protected PasswordEncoder getPasswordEncoder() {
return this.passwordEncoder;
}
public void setUserDetailsService(CustomUserDetailsService customUserDetailsService) {
this.userDetailsService = customUserDetailsService;
}
protected CustomUserDetailsService getUserDetailsService() {
return this.userDetailsService;
}
public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
this.userDetailsPasswordService = userDetailsPasswordService;
}
}
Pass the parameters unchanged to CustomUserDetailsService, This class has been configured at the beginning of the article .
We can see the successful implementation of our loadUserByUsername Method , All parameters are acceptable . This concludes the interpretation of the source code for authentication and authorization .
Attached sheet
CaptchaTokenGranter Source code is as follows
package com.zzh.oauth2_auth.component;
import com.alibaba.druid.util.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import java.util.LinkedHashMap;
import java.util.Map;
public class CaptchaTokenGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "captcha";
private final AuthenticationManager authenticationManager;
private StringRedisTemplate redisTemplate;
public CaptchaTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, AuthenticationManager authenticationManager, StringRedisTemplate redisTemplate
) {
super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
this.authenticationManager = authenticationManager;
this.redisTemplate = redisTemplate;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = new LinkedHashMap(tokenRequest.getRequestParameters());
String captcha = parameters.get("captcha");
if (!"1".equals(captcha) || StringUtils.isEmpty(captcha)) throw new InvalidGrantException(" Verification code error ");
String username = parameters.get("username");
String password = parameters.get("password");
// The same logic as password mode
Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
try {
userAuth = this.authenticationManager.authenticate(userAuth);
} catch (AccountStatusException var8) {
throw new InvalidGrantException(var8.getMessage());
} catch (BadCredentialsException var9) {
throw new InvalidGrantException(var9.getMessage());
}
if (userAuth != null && userAuth.isAuthenticated()) {
OAuth2Request storedOAuth2Request = this.getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
} else {
throw new InvalidGrantException("Could not authenticate user: " + username);
}
}
}
CustomAuthenticationProvider Source code is as follows
package com.zzh.oauth2_auth.component;
import com.zzh.oauth2_auth.service.CustomUserDetailsService;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.Assert;
import java.util.Map;
public class CustomAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
private PasswordEncoder passwordEncoder;
private volatile String userNotFoundEncodedPassword;
private UserDetailsPasswordService userDetailsPasswordService;
public CustomAuthenticationProvider() {
this.setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
} else {
String presentedPassword = authentication.getCredentials().toString();
if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
this.logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
}
protected void doAfterPropertiesSet() {
Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
}
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
this.prepareTimingAttackProtection();
Map<String,String> map = (Map<String, String>) authentication.getDetails(); // Custom add
try {
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(map);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
} else {
return loadedUser;
}
} catch (UsernameNotFoundException var4) {
this.mitigateAgainstTimingAttack(authentication);
throw var4;
} catch (InternalAuthenticationServiceException var5) {
throw var5;
} catch (Exception var6) {
throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
}
}
private CustomUserDetailsService userDetailsService;
protected CustomUserDetailsService getUserDetailsService() {
return this.userDetailsService;
}
public void setUserDetailsService(CustomUserDetailsService customUserDetailsService) {
this.userDetailsService = customUserDetailsService;
}
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
boolean upgradeEncoding = this.userDetailsPasswordService != null && this.passwordEncoder.upgradeEncoding(user.getPassword());
if (upgradeEncoding) {
String presentedPassword = authentication.getCredentials().toString();
String newPassword = this.passwordEncoder.encode(presentedPassword);
user = this.userDetailsPasswordService.updatePassword(user, newPassword);
}
return super.createSuccessAuthentication(principal, authentication, user);
}
private void prepareTimingAttackProtection() {
if (this.userNotFoundEncodedPassword == null) {
this.userNotFoundEncodedPassword = this.passwordEncoder.encode("userNotFoundPassword");
}
}
private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
}
}
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
this.passwordEncoder = passwordEncoder;
this.userNotFoundEncodedPassword = null;
}
protected PasswordEncoder getPasswordEncoder() {
return this.passwordEncoder;
}
public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
this.userDetailsPasswordService = userDetailsPasswordService;
}
}
obtain token The interface source code is as follows
package com.zzh.oauth2_auth.controller;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.nimbusds.jose.JWSObject;
import com.zzh.common.constant.AuthConstants;
import com.zzh.common.constant.RedisConstants;
import com.zzh.common.result.Result;
import com.zzh.oauth2_auth.domain.dto.Oauth2TokenDto;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
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;
/** * Customize Oauth2 Get token interface */
@Api(tags = " authentication center ")
@Slf4j
@RequestMapping("/oauth")
@RestController
@RefreshScope
public class AuthController {
@Autowired
private TokenEndpoint tokenEndpoint;
@Autowired
private RedisTemplate redisTemplate;
@ApiOperation(value = " obtain token", notes = "")
@PostMapping("/token")
public Result getToken(@RequestParam Map<String, String> parameters, Principal principal) throws Exception {
String username = parameters.get("username");
parameters.put("password", ""); //require
OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
Oauth2TokenDto oauth2TokenDto = Oauth2TokenDto.builder()
.token(oAuth2AccessToken.getValue())
.refreshToken(oAuth2AccessToken.getRefreshToken().getValue())
.expiresIn(oAuth2AccessToken.getExpiresIn())
.tokenHead("Bearer ").build();
JWSObject jwsObject = JWSObject.parse(oAuth2AccessToken.getValue());
String payload = jwsObject.getPayload().toString();
JSONObject jsonObject = JSONUtil.parseObj(payload);
String jti = jsonObject.getStr(AuthConstants.JWT_JTI);
redisTemplate.opsForValue().set(RedisConstants.USER_LOGIN_JTI + jti, username);
log.info("Response-> url =/token,Parameters:{" + parameters + "},Result:{" + oauth2TokenDto + "}");
return Result.success(oauth2TokenDto);
}
}
UserDetailsServiceImpl Source code is as follows
```javascript
package com.zzh.oauth2_auth.service.impl;
import com.zzh.oauth2_auth.constant.MessageConstant;
import com.zzh.oauth2_auth.domain.SecurityUser;
import com.zzh.oauth2_auth.service.CustomUserDetailsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.CredentialsExpiredException;
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.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
@Slf4j
@Service
public class UserDetailsServiceImpl implements CustomUserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(Map<String, String> map) throws UsernameNotFoundException {
SecurityUser securityUser = new SecurityUser();
securityUser.setUsername(map.get("username"));
securityUser.setPassword(passwordEncoder.encode(""));
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ADMIN"));
System.err.println(" The currently logged in role is :ADMIN");
securityUser.setAuthorities(authorities);
if (!securityUser.isEnabled()) {
throw new DisabledException(MessageConstant.ACCOUNT_DISABLED);
} else if (!securityUser.isAccountNonLocked()) {
throw new LockedException(MessageConstant.ACCOUNT_LOCKED);
} else if (!securityUser.isAccountNonExpired()) {
throw new AccountExpiredException(MessageConstant.ACCOUNT_EXPIRED);
} else if (!securityUser.isCredentialsNonExpired()) {
throw new CredentialsExpiredException(MessageConstant.CREDENTIALS_EXPIRED);
}
return securityUser;
}
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
return null;
}
}
package com.zzh.oauth2_auth.service;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import java.util.Map;
/** * Inherit the original UserDetailsService New custom method */
public interface CustomUserDetailsService extends UserDetailsService {
UserDetails loadUserByUsername(Map<String,String> map) throws UsernameNotFoundException;
}
边栏推荐
- Docker installs redis-5.0.12. Detailed steps
- How to automatically remove all . orig files in Mercurial working tree?
- 证件照处理
- LeetCode Algorithm 剑指 Offer 52. 两个链表的第一个公共节点
- cat写多行内容到文件
- Can AI chat robots replace manual customer service?
- Valueerror: cannot take a larger sample than population when 'replace=false‘
- Leetcode: push domino (domino simulation)
- Common voting governance in Dao
- 04A interrupt configuration
猜你喜欢
envoy获取客户端真实IP
Cross border e-commerce, early entry and early benefit
Technology Review: what is the evolution route of container technology? What imagination space is there in the future?
Online filing process
[personal experiment report]
2022-06-10 work record --js- obtain the date n days after a certain date
Chapter 10 project communication management
Description of software version selection of kt6368a Bluetooth dual-mode transparent chip
CA Zhouji - the first lesson in 2022 rust
NiO, bio, AIO
随机推荐
Programmers become gods by digging holes in one year, carrying flags in five years and becoming gods in ten years
Future development of education industry of e-commerce Express
Redis-跳表
YGG 近期游戏合作伙伴一览
The usage difference between isempty and isblank is so different that so many people can't answer it
CDN principle
AQS source code analysis
Panorama of enterprise power in China SSD industry
Redis hop table
磁盘的结构
中国SSD行业企业势力全景图
Cache control of HTTP
进程的通信方式
[QT] QT event handling
In the era of full programming, should I give up this road?
Power system | IEEE paper submission process
interrupt、interrupted 、isInterrupted 区别
envoy获取客户端真实IP
CA Zhouji - the first lesson in 2022 rust
Genesis public chain and a group of encryption investors in the United States gathered in consensus 2022