自定义的方式,使用oauth2生成token+token续命

项目放在gitee

https://gitee.com/hunterhyl/common-techniques4.git 下面的oauth2模块

建议:使用oauth2之前先看一些基本介绍,里面有一个概念还是需要了解的

依赖

        11
        11
        11
        UTF-8
        UTF-8
        2.7.11
    
    

        
            org.springframework.boot
            spring-boot-starter-data-redis
            2.7.11
        

        
            org.springframework.boot
            spring-boot-starter
            2.7.11
        

        
            org.springframework.boot
            spring-boot-starter-web
            2.7.11
        

        
            org.projectlombok
            lombok
            1.18.22
            true
        

        
            com.baomidou
            mybatis-plus-boot-starter
            3.5.3
        

        
            mysql
            mysql-connector-java
            8.0.28
        


        
            com.alibaba.fastjson2
            fastjson2
            2.0.25
        



        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
            org.springframework.cloud
            spring-cloud-starter-oauth2
            2.2.4.RELEASE
            
                
                    bcpkix-jdk15on
                    org.bouncycastle
                
                
                    spring-cloud-starter
                    org.springframework.cloud
                
            
        


    

    
        
            
                org.springframework.boot
                spring-boot-dependencies
                ${spring-boot.version}
                pom
                import
            
        
    

配置文件

spring.application.name=auth-service
server.port=8080

spring.redis.host=localhost
spring.redis.port=6379

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=填写自己的
spring.datasource.url=jdbc:mysql://localhost:3306/填写自己的?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
mybatis-plus.mapper-locations=classpath:mapper/*.xml

项目结构

在这里插入图片描述

思路

首先是生成token的入口在哪里,找到入口之口就能够缺啥补啥

tokenEndpoint.postAccessToken()是入口

在这里插入图片描述

缺少一个 Principal 个一个 Map,点进去看看,第一行就发现其实要的是 Authentication 类

在这里插入图片描述

再看看这个 Authentication 类,其实是个接口,那么 使用 ctrl+H 看一下框架中原本有哪些实现类

在这里插入图片描述

实现类很多,那么我的做法是:找到一个实现类,然后复制一份,改成适合自己的项目

我选择的是 UsernamePasswordAuthenticationToken 这个实现类,复制一份,就是 JiuBoDouUsernamePasswordAuthenticationToken 这个实现类,至于要改什么东西,先不着急

现在第一步已经可以继续执行下去了,new出我们自己的实现类,和一个空的 map 传到 postAccessToken()里面,继续debug下去

但是new JiuBoDouUsernamePasswordAuthenticationToken 的时候,我们又发现:JiuBoDouUsernamePasswordAuthenticationToken 有两个构造方法,用哪个呢?

    public JiuBoDouUsernamePasswordAuthenticationToken(Object principal, Object credentials) {
        super((Collection)null);
        this.principal = principal;
        this.credentials = credentials;
        this.setAuthenticated(false);//这里
    }
        public JiuBoDouUsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true);//这里
    }

这两个方法还是很明显不一样的,我们选择参数多的这个,那么Collection authorities该怎么写,还是先不着急,传递一个空的进去就行

public class JiuBoDouUsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
    private static final long serialVersionUID = 570L;
    private final Object principal;//这个是什么意思
    private Object credentials;//这个又是什么意思

在构造方法的时候,这两个值是需要传的,那么传什么呢? 先理解成 principal就是用户的登录手机号,或者说是唯一标识 credentials就是用户输入的密码

于是就有了这样的代码:

    @GetMapping("/login/{username}/{password}")
    public String login(@PathVariable("username") String username, @PathVariable("password") String password) throws HttpRequestMethodNotSupportedException {
        Map map = new HashMap();
        map.put("client_id", "jiubodou_client_id");//map里面放的数据是后面debug的时候发现需要这些参数,又回到这里补上去的,正常来说按照我上面的思路这里的map其实是空的,什么数据都没有的
        map.put("grant_type", "jiubodou_grant_type");
        map.put("username", username);
        map.put("password", password);

        JiuBoDouUsernamePasswordAuthenticationToken authenticationToken =
                new JiuBoDouUsernamePasswordAuthenticationToken(username, password,
                        new ArrayList()); 
        ResponseEntity postedAccessToken = tokenEndpoint.postAccessToken(authenticationToken,
                map);

        return Objects.requireNonNull(postedAccessToken.getBody()).getValue();
    }

好了,现在继续debug

在这里插入图片描述

这一行,进去看看

在这里插入图片描述

发现这里如果不使用三个参数的构造方法的话,这里就会抛错,所以我们不能用两个参数的构造方法,继续,看这里

在这里插入图片描述

这里的 client.getName方法是走的JiuBoDouUsernamePasswordAuthenticationToken父类的方法,如下:

    public String getName() {
        if (this.getPrincipal() instanceof UserDetails) {
            return ((UserDetails)this.getPrincipal()).getUsername();
        } else if (this.getPrincipal() instanceof AuthenticatedPrincipal) {
            return ((AuthenticatedPrincipal)this.getPrincipal()).getName();
        } else if (this.getPrincipal() instanceof Principal) {
            return ((Principal)this.getPrincipal()).getName();
        } else {
            return this.getPrincipal() == null ? "" : this.getPrincipal().toString();
        }
    }

此时如果积雪debug下去,就会发现最终的clientId拿到的是用户用于登录的手机号,那么这肯定是不对的,用户用于登陆的手机号怎么又变成了 clientId呢?所以我的方法是,在JiuBoDouUsernamePasswordAuthenticationToken里面重写 getName方法,如下

    @Override
    public String getName() {
        return "jiubodou_client_id";
    }

这样的话就会有如下效果

在这里插入图片描述

继续:

在这里插入图片描述

见名知意:根据 clientId 加载信息,那么问题就随之而来,去哪里加载?数据库么?如果是数据库的话,我好像没告诉oauth2数据库连接是哪里吧,表又是哪一个吧

如果此时去看 this.getClientDetailsService()的结果会发现,给的是 InMemoryClientDetailsService ,也就是从内存中拿。这显然不是我们希望的,其实去看一眼就知道,oauth2是给我们准备了数据库连接的

在这里插入图片描述

就是下面这个 JdbcClientDetailsService 那么如何启用,如何配置?表怎么建? 如下:

@Configuration
public class JiuBoDouOauth2Config extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
        clients.withClientDetails(jdbcClientDetailsService);
    }

}
-- auto-generated definition
create table oauth_client_details
(
    client_id               varchar(128)  not null comment '客户端ID'
        primary key,
    resource_ids            varchar(256)  null comment '资源ID集合,多个资源时用英文逗号分隔',
    client_secret           varchar(256)  null comment '客户端密匙',
    scope                   varchar(256)  null comment '客户端申请的权限范围',
    authorized_grant_types  varchar(256)  null comment '客户端支持的grant_type',
    web_server_redirect_uri varchar(256)  null comment '重定向URI',
    authorities             varchar(256)  null comment '客户端所拥有的SpringSecurity的权限值,多个用英文逗号分隔',
    access_token_validity   int           null comment '访问令牌有效时间值(单位秒)',
    refresh_token_validity  int           null comment '更新令牌有效时间值(单位秒)',
    additional_information  varchar(4096) null comment '预留字段',
    autoapprove             varchar(256)  null comment '用户是否自动Approval操作'
)
    comment '客户端信息' charset = utf8mb3
                         row_format = DYNAMIC;

附赠一条数据

INSERT INTO cloud_order.oauth_client_details (client_id, resource_ids, client_secret, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove) VALUES ('jiubodou_client_id', null, 'cfc428696a9bca6321b629bbcfb8ddd6', 'all', 'jiubodou_grant_type', null, null, 3600, 604800, null, '1');

继续看

在这里插入图片描述

这里执行的代码是 DefaultOAuth2RequestFactory 里面的 createTokenRequest 方法

    public TokenRequest createTokenRequest(Map requestParameters, ClientDetails authenticatedClient) {
        String clientId = (String)requestParameters.get("client_id");//这里说明最开始的map中需要client_id
        if (clientId == null) {
            clientId = authenticatedClient.getClientId();
        } else if (!clientId.equals(authenticatedClient.getClientId())) {
            throw new InvalidClientException("Given client ID does not match authenticated client");
        }

        String grantType = (String)requestParameters.get("grant_type");//这里说明最开始的map中需要grant_type
        Set scopes = this.extractScopes(requestParameters, clientId);
        TokenRequest tokenRequest = new TokenRequest(requestParameters, clientId, scopes, grantType);
        return tokenRequest;
    }

继续:

在这里插入图片描述

这里是拿到生成器去生成 token,进去看一下,getTokenGranter()得到的是 CompositeTokenGranter

再去看 CompositeTokenGranter.grant 方法:

    public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
        Iterator var3 = this.tokenGranters.iterator();//可以看出来,是一个 遍历操作  也就是说在CompositeTokenGranter这个类中,有一个tokenGranters集合,里面存放着所有的 tokenGranter,该方法的作用就是挨个去执行每一个tokenGranter里面的grant方法。

        OAuth2AccessToken grant;
        do {
            if (!var3.hasNext()) {
                return null;
            }

            TokenGranter granter = (TokenGranter)var3.next();//可以看出来,是一个 遍历操作  也就是说在CompositeTokenGranter这个类中,有一个tokenGranters集合,里面存放着所有的 tokenGranter,该方法的作用就是挨个去执行每一个tokenGranter里面的grant方法。
            grant = granter.grant(grantType, tokenRequest);
        } while(grant == null);

        return grant;
    }

那么继续看下去

在这里插入图片描述

这里就是拿到具体的一个 tokenGranter,然后执行grant方法。那么问题显而易见,我们上面自定义了 grant_type=jiubodou_grant_type

。那么问题是这里面有没有 哪一个 granter是为jiubodou_grant_type这个生成类型服务的granter呢?很显然是没有的

我们进到 grant 这个方法里面

    public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
        if (!this.grantType.equals(grantType)) {  //这里就去匹配,当前的这个 granter 是不是用来生成 jiubodou_grant_type的,如果不是,就直接返回null,在此应证了 当前的系统中是没有 granter 来生成我们的token的,那么我们需要自己去建立一个
            return null;
        } else {
            String clientId = tokenRequest.getClientId();
            ClientDetails client = this.clientDetailsService.loadClientByClientId(clientId);
            this.validateGrantType(grantType, client);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Getting access token for: " + clientId);
            }

            return this.getAccessToken(client, tokenRequest);
        }
    }

继续,那么我们应该去建造一个 专门用来生成 jiubodou_grant_type 类型的 granter ,依旧是,找到现成的实现类,然后复制,然后改动,下面是系统中现有的实现类

在这里插入图片描述

我直接复制了 ResourceOwnerPasswordTokenGranter当作自己的 JiuBoDouResourceOwnerPasswordTokenGranter。然后改动一些东西,首先看一下 ResourceOwnerPasswordTokenGranter

public class ResourceOwnerPasswordTokenGranter extends AbstractTokenGranter {
    private static final String GRANT_TYPE = "password";  //这里需要改吧
    private final AuthenticationManager authenticationManager;

    public ResourceOwnerPasswordTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
        this(authenticationManager, tokenServices, clientDetailsService, requestFactory, "password");//这里需要改吧
    }

    protected ResourceOwnerPasswordTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
        super(tokenServices, clientDetailsService, requestFactory, grantType);
        this.authenticationManager = authenticationManager;
    }

    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
        Map parameters = new LinkedHashMap(tokenRequest.getRequestParameters());
        String username = (String)parameters.get("username");//这里告诉我们 最开始的map中需要放 username
        String password = (String)parameters.get("password");//这里告诉我们 最开始的map中需要放 password
        parameters.remove("password");
        Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);//这里需要改吧
        ((AbstractAuthenticationToken)userAuth).setDetails(parameters);

        Authentication userAuth;
        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);
        }
    }
}

那么改完之后还需要放到 CompositeTokenGranter里面的那个集合里面对吧

@Configuration
public class JiuBoDouOauth2Config extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    /*使用 jdbc 来查询数据库中的 client 信息*/
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
        clients.withClientDetails(jdbcClientDetailsService);
    }


    @Autowired   //是我们自己放到容器中的,看下去 不着急
    private JiuBoDouTokenService jiuBoDouTokenService;

    /*添加自己的 TokenGranter*/
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        ArrayList tokenGranters = new ArrayList();
     //看到这个里先别着急,看下面的说明
        JiuBoDouResourceOwnerPasswordTokenGranter jiuBoDouResourceOwnerPasswordTokenGranter =
                new JiuBoDouResourceOwnerPasswordTokenGranter(
                        authenticationManager,
                        jiuBoDouTokenService,
                        endpoints.getClientDetailsService(),
                        endpoints.getOAuth2RequestFactory()
                );

        tokenGranters.add(jiuBoDouResourceOwnerPasswordTokenGranter);

        CompositeTokenGranter compositeTokenGranter = new CompositeTokenGranter(tokenGranters);

        endpoints.tokenGranter(compositeTokenGranter);
    }

}

上面我放的是成形之后的代码,但是一开始不是这样的,当我们有了自己的 jiuBoDouResourceOwnerPasswordTokenGranter 之后,new出来的时候就发现了很多问题,会发现好多入参啊,而且都不认识,怎么办?

在这里插入图片描述

不着急,先看第一个入参,AuthenticationManager authenticationManager,在我们之前的描述中完全没听过这个东西,怎么办?还是找一个现成的实现类,复制一份改一改么?这个不用。我们这样就可以了:

@Configuration
public class JiuBoDouWebConfig extends WebSecurityConfigurerAdapter {

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }//这里  实际放到容器中的是一个 WebSecurityConfigurerAdapter

    @Override    //这段代码就不用详细说了吧
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/oauth2/**").permitAll()
                .anyRequest().authenticated();
        http.cors();/*允许跨域*/
    }
}

直接将框架中自带的一个 AuthenticationManager 放入到容器中,然后在需要的地方直接注入,就解决了第一个参数

继续,看第二个参数:AuthorizationServerTokenServices tokenServices,这个也是没见过的东西,我依旧是找现成的实现类,复制一份,于是就有了 自己的 JiuBoDouTokenService,同样的,也把自己的 JiuBoDouTokenService 创建出来放到容器中

@Configuration
public class TokenServiceConfig {

    @Autowired  //是我们自己注入到容器中的,看下去不着急
    private RedisTokenStore redisTokenStore;

    @Bean
    public JiuBoDouTokenService jiuBoDouTokenService() {
        JiuBoDouTokenService jiuBoDouTokenService = new JiuBoDouTokenService();
        jiuBoDouTokenService.setSupportRefreshToken(true);
        jiuBoDouTokenService.setTokenStore(redisTokenStore);
        return jiuBoDouTokenService;
    }
}

而这里的

@Autowired

private RedisTokenStore redisTokenStore;

这么获取:

@Configuration
public class RedisTokenStoreConfig {

    @Autowired  //这个是能直接注入的,框架中自带的
    private RedisConnectionFactory redisConnectionFactory;

    @Bean   
    public RedisTokenStore redisTokenStore() {
        return new RedisTokenStore(redisConnectionFactory);
    }

}

有点绕对不对,那么就倒着看一下, 先有了框架中自带的RedisConnectionFactory 于是有了自己的RedisTokenStore ,

因为有了 RedisTokenStore ,就能有 JiuBoDouTokenService

因为有了JiuBoDouTokenService ,所以第二个参数就解决了

继续,看第三个参数:

ClientDetailsService clientDetailsService 和 OAuth2RequestFactory requestFactory

ClientDetailsService 还记得把,我们之前就用 jdbc 代替了 内存中的查找

而OAuth2RequestFactory 直接使用默认的就行

所以才有了最终的代码

好的,我们重新回到 grant方法,现在 我们自己的 granter也放到了框架中了,终于能跨过

在这里插入图片描述

这一步了对吧,继续 debug下去,来到这一步:进去

在这里插入图片描述

来到了这里:

在这里插入图片描述

我们看 this.getOAuth2Authentication(client, tokenRequest) 这个方法,进到的是我们自己 granter 里面的方法,断点别打错了

    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
        Map parameters = new LinkedHashMap(tokenRequest.getRequestParameters());
        String username = (String)parameters.get("username");//最开始的map中需要 username
        String password = (String)parameters.get("password");//最开始的map中需要 password
        parameters.remove("password");
        Authentication userAuth = new JiuBoDouUsernamePasswordAuthenticationToken(username, password);//用我们自己的JiuBoDouUsernamePasswordAuthenticationToken,而且这里用的是两个参数的,而不是最开始三个参数的
        ((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);
        }
    }

来到这里

在这里插入图片描述

点进去,这里有点难度,慢慢来

        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            if (this.delegate != null) { //如果debug的话,会发现这里delegate 是null,也就是说会走到 else里面
                return this.delegate.authenticate(authentication);
            } else {
                synchronized(this.delegateMonitor) {
                    if (this.delegate == null) {
                        this.delegate = (AuthenticationManager)this.delegateBuilder.getObject();//这里就会用delegateBuilder构造一下delegate 
                        this.delegateBuilder = null; //delegate 构建完成后就把delegateBuilder 置为null,也就是说这个delegateBuilder 是一次性的
                    }
                }

                return this.delegate.authenticate(authentication);//因为通过delegateBuilder 构建了delegate ,那么这里就能继续下去
            }
        }

进到 this.delegate.authenticate(authentication)的authenticate方法里面,代码很多,但是不用急

    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Class toTest = authentication.getClass();  //获取类,是我们自己写的JiuBoDouUsernamePasswordAuthenticationToken 这个类
        AuthenticationException lastException = null;
        AuthenticationException parentException = null;
        Authentication result = null;
        Authentication parentResult = null;
        int currentPosition = 0;
        int size = this.providers.size();
        Iterator var9 = this.getProviders().iterator();//看起来又是一个遍历

        while(var9.hasNext()) {
            AuthenticationProvider provider = (AuthenticationProvider)var9.next();
            if (provider.supports(toTest)) {  //这里就是去看 当前这个 provider是不是支持 JiuBoDouUsernamePasswordAuthenticationToken 不用想了 很定不支持
                if (logger.isTraceEnabled()) {
                    Log var10000 = logger;
                    String var10002 = provider.getClass().getSimpleName();
                    ++currentPosition;
                    var10000.trace(LogMessage.format("Authenticating request with %s (%d/%d)", var10002, currentPosition, size));
                }

                try {
                    result = provider.authenticate(authentication);
                    if (result != null) {
                        this.copyDetails(authentication, result);
                        break;
                    }
                } catch (InternalAuthenticationServiceException | AccountStatusException var14) {
                    this.prepareException(var14, authentication);
                    throw var14;
                } catch (AuthenticationException var15) {
                    lastException = var15;
                }
            }
        }

        if (result == null && this.parent != null) {
            try {
                parentResult = this.parent.authenticate(authentication); //这边还有,去找父类的 provider 是不是有能支持JiuBoDouUsernamePasswordAuthenticationToken 的
                result = parentResult;
            } catch (ProviderNotFoundException var12) {
            } catch (AuthenticationException var13) {
                parentException = var13;
                lastException = var13;
            }
        }

        if (result != null) {
            if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
                ((CredentialsContainer)result).eraseCredentials();
            }

            if (parentResult == null) {
                this.eventPublisher.publishAuthenticationSuccess(result);
            }

            return result;
        } else {
            if (lastException == null) {
                lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
            }

            if (parentException == null) {
                this.prepareException((AuthenticationException)lastException, authentication);
            }

            throw lastException;
        }
    }

下面我们就去写一个自己的 provider 并放到框架中 同样的,复制一份 改成自己的

在这里插入图片描述

下面是成型的 JiuBoDouWebConfig 代码

@Configuration
public class JiuBoDouWebConfig extends WebSecurityConfigurerAdapter {

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/oauth2/**").permitAll()
                .anyRequest().authenticated();
        http.cors();/*允许跨域*/
    }

    @Autowired    //下面会有说明
    UserDetailsService jiuBoDouUserDetailService;

    @Override
    protected void configure(AuthenticationManagerBuilder builder) {
//这里就是添加自己的 
        JiuBoDouDaoAuthenticationProvider provider = new JiuBoDouDaoAuthenticationProvider();
        provider.setPasswordEncoder(new BCryptPasswordEncoder());
        provider.setUserDetailsService(jiuBoDouUserDetailService);
        provider.setUserDetailsPasswordService(new JiuBoDouUserPasswordService());
        provider.setHideUserNotFoundExceptions(false);
        builder.authenticationProvider(provider);
    }
}

重新来一次,断点打在

在这里插入图片描述

就能看到自己写的 provider了,进去

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(JiuBoDouUsernamePasswordAuthenticationToken.class, authentication, () -> {
            return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only JiuBoDouUsernamePasswordAuthenticationToken is supported");
        });
        String username = this.determineUsername(authentication);
        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;

            try {
                user = this.retrieveUser(username, (JiuBoDouUsernamePasswordAuthenticationToken)authentication);
            } catch (UsernameNotFoundException var6) {
                this.logger.debug("Failed to find user '" + username + "'");
                if (!this.hideUserNotFoundExceptions) {
                    throw var6;
                }

                throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
            }

            Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
        }

        try {
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (JiuBoDouUsernamePasswordAuthenticationToken)authentication);
        } catch (AuthenticationException var7) {
            if (!cacheWasUsed) {
                throw var7;
            }

            cacheWasUsed = false;
            user = this.retrieveUser(username, (JiuBoDouUsernamePasswordAuthenticationToken)authentication);
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (JiuBoDouUsernamePasswordAuthenticationToken)authentication);//这里也需要看一下
        }

        this.postAuthenticationChecks.check(user);
        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;
        if (this.forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }

        return this.createSuccessAuthentication(principalToReturn, authentication, user);
    }

来到这里

在这里插入图片描述

进去

protected final UserDetails retrieveUser(String username, JiuBoDouUsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        this.prepareTimingAttackProtection();

        try {
            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);//这里进去
            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);
        }
    }

发现 this.getUserDetailsService().loadUserByUsername(username) 又出现了一个不知道的东西,UserDetailsService 是什么?

public interface UserDetailsService {
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

很简单的一个接口,我们自己写一个,实现即可

@Service
public class JiuBoDouUserDetailService implements UserDetailsService {

    @Autowired
    private UserTableMapper userTableMapper;

    @Override//这里其实就是拿着用户传来的 username 去数据库中查这个用户。但是返回值是UserDetails 
    public UserDetails loadUserByUsername(String userMobile) throws UsernameNotFoundException {
        ArrayList simpleGrantedAuthorities = new ArrayList();
        simpleGrantedAuthorities.add(new SimpleGrantedAuthority("admin"));

        LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper();
        queryWrapper.eq(UserTable::getUserMobile, userMobile);
        UserTable userTable = userTableMapper.selectOne(queryWrapper);
        JiuBoDouUserDetails jiuBoDouUserDetails = new JiuBoDouUserDetails(userTable.getUserMobile()
                , userTable.getUserPassword(), simpleGrantedAuthorities);

        return jiuBoDouUserDetails;
    }
}

继续看 UserDetails

@Data
@AllArgsConstructor
@NoArgsConstructor
public class JiuBoDouUserDetails implements UserDetails {

    private String username;/*应该说是 用户的唯一标识 可以是登录时候用的手机号 也可以是id*/

    private String password;/*这里的密码是数据库中查出来的 加密之后的密码 不是明文*/

    private Collection authorities;

    @Override
    public Collection getAuthorities() {
        return authorities;
    }

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

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

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

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

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

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

这里就不细讲,代码都很简单。看一下就能明白了,然后就是将 自己写的JiuBoDouUserDetailService 放到框架中,之前已经放过了

继续看下面:

在这里插入图片描述

    protected void additionalAuthenticationChecks(UserDetails userDetails, JiuBoDouUsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        if (authentication.getCredentials() == null) {
            this.logger.debug("Failed to authenticate since 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("Failed to authenticate since password does not match stored value");
                throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
            }
        }
    }

本文来自网络,不代表协通编程立场,如若转载,请注明出处:https://net2asp.com/cb396bc9c2.html