自定义的方式,使用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
