当前位置:网站首页>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
 Insert picture description here
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 ,
 Insert picture description here
 Insert picture description here
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 .
 Insert picture description here
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 .
 Insert picture description here

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 .
 Insert picture description here

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 .
 Insert picture description here
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 .
 Insert picture description 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
 Insert picture description here
 Insert picture description here

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 .
 Insert picture description here
Since I have extended the authentication logic myself, it will eventually be implemented to CustomAuthenticationProvider Medium retrieveUser() In the method .
 Show

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 .
 Insert picture description here
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 .

 Insert picture description here

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;

}
原网站

版权声明
本文为[Zhang Zixing's blog]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/175/202206241646375543.html