模式
password模式
pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>oauth2-demo</groupId>
<artifactId>oauth2-demo</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<springSecurityOauth>2.3.3.RELEASE</springSecurityOauth>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- <dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 -->
<!-- <dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>${springSecurityOauth}</version>
</dependency>-->
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
<!-- redis中 不用可以去除-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- mysql连接 -->
<!--<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>-->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
权限服务器配置
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
AuthenticationManager authenticationManager;
@Autowired
RedisConnectionFactory redisConnectionFactory;
@Bean
public RedisClientDetailsService redisClientDetailsService() {
RedisClientDetailsService r = new RedisClientDetailsService();
return r;
}
@Autowired
private TokenStore getRedisTokenStore() {
return new MyRedisTokenStore(redisConnectionFactory);
}
@Autowired
private UserDetailsService userDetailsService;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// clients.inMemory()
// .withClient("client_1")
// .resourceIds(DEMO_RESOURCE_ID)
// .authorizedGrantTypes("client_credentials", "refresh_token")
// .scopes("all")
// .authorities("client")
// .secret("{bcrypt}" + new BCryptPasswordEncoder().encode("123456"));
clients.withClientDetails(redisClientDetailsService());
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(getRedisTokenStore()) // 使用自定义Redis token,新版的redis去除了set方法
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
//允许表单认证
oauthServer.allowFormAuthenticationForClients();
}
}
1.配置客户端验证和用户验证实现类
2.配置tokenstore(我们使用redis作为token存储介质)
自定义TokenStore(redis)
可以使用spring security自带的RedisTokenStore,但是由于redis版本原因,会执行报错,重写后在set前加stringCommands。
该类中包含token验证、权限判断等,无需再次改动。
资源服务器配置
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/ignore").permitAll()
.antMatchers("/user").hasAuthority("ADMIN")
.antMatchers("/user2").hasAuthority("ADMIN2")
.anyRequest().authenticated();
}
}
permitAll():表示匿名访问
hasAuthority(“ADMIN”):表示需要admin权限
WEB安全配置
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled =true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
protected void configure(HttpSecurity http) throws Exception {
//http.csrf().disable();
// 匹配所有请求都不需要权限控制
// http.authorizeRequests().anyRequest().access("permitAll");
//匹配所有的请求都需要USER权限
//http.authorizeRequests().anyRequest().hasRole("USER");
//http.authorizeRequests().antMatchers("/api/**").authenticated();
}
// 使用 用户登录的加密策略
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
用户验证
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (!username.equals("ld")) {
throw new AuthenticationCredentialsNotFoundException("用户不存在");
}
UserDetails build = User.withUsername(username).password("$2a$10$/xdCNqcVOtPTxiM1BARhkOWJGHFC2t8nnySPpXtMvB5U3qGOejGj6")
// 这里不能roles和authorities一起使用,会覆盖类中list,要一起使用在ROLE判断前面加ROLE_
.authorities("ROLE_LD","ADMIN").build();
return build;
}
}
示例中写死用户名、密码、权限,实际中需要查库。
.authorities(“ROLE_LD”,“ADMIN”),这里ROLE_LD,表示用户拥有LD角色,不写ROLE_开头,表示直接拥有某权限。
客户端验证
public class RedisClientDetailsService implements ClientDetailsService {
@Override
public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
BaseClientDetails b = new BaseClientDetails();
// 这里写正确的clientID和secret
// 从数据库查询后封装BaseClientDetails对象,后面会通过DaoAuthenticationProvider来比较传入的值与此对象的值
b.setClientId(clientId);
b.setClientSecret("$2a$10$06msMGYRH8nrm4iVnKFNKOoddB8wOwymVhbUzw/d3ZixD7Nq8ot72");
Collection<String> authorizedGrantTypes = new HashSet<String>();
authorizedGrantTypes.add("password");
authorizedGrantTypes.add("refresh_token");
b.setAuthorizedGrantTypes(authorizedGrantTypes );
Collection<String> scope = new HashSet<>();
scope.add("select");
b.setScope(scope);
return b;
}
}
本示例中写死客户端信息(clientId、secret、scope、权限等),实际中根据业务,可以查询数据库或者缓存,匹配传入的clientId查询数据返回客户端对象。
调试
- 获取token
访问接口
http://localhost:9090/oauth/token?username=ld&password=123456&grant_type=password&scope=select&client_id=client_2&client_secret=webApp
核心处理类TokenEndpoint、AuthorizationEndpoint(源码不分析了,可以参照其他博主的文章),
先进入我们自定义的clientDetailService,由于我们测试使用的password模式,后面会进入自定义UserDetailService。
此接口返回数据格式:
{
"access_token": "e9fad28a-21ec-4ff7-b78b-7ff2a47c0ef5",
"token_type": "bearer",
"refresh_token": "fa7bdc1e-c89e-4464-b801-7883e9797c84",
"expires_in": 42880,
"scope": "select"
}
access_token:用户token,访问资源需要携带此token
refresh_token:用于调用刷新token,需要携带此token
- 调用业务接口
http://localhost:9090/role
请求头
Authorization=Bearer e9fad28a-21ec-4ff7-b78b-7ff2a47c0ef5
@RequestMapping("/role")
@PreAuthorize("hasRole('LD')")
public String role() {
return "role";
}
@PreAuthorize(“hasRole(‘LD’)”)
调用自定义redisTokenStore来验证权限
- 刷新token
http://localhost:9090/oauth/token?refresh_token=fa7bdc1e-c89e-4464-b801-7883e9797c84&grant_type=refresh_token&client_id=client_2&client_secret=webApp
可以重置expires_in过期时间
源码分析
1. 客户端验证
http://localhost:9090/oauth/token?username=ld&password=123456&grant_type=password&scope=select&client_id=client_2&client_secret=webApp
拦截器
在FilterChainProxy的doFilter中开始
private List<SecurityFilterChain> filterChains;
我们关注 ClientCredentialsTokenEndpointFilter这个类
public class ClientCredentialsTokenEndpointFilter extends AbstractAuthenticationProcessingFilter {
继承了AbstractAuthenticationProcessingFilter ,首先会先到父类的doFilter
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
authResult = attemptAuthentication(request, response); // 1
if (authResult == null) {
// return immediately as subclass has indicated that it hasn't completed
// authentication
return;
}
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (InternalAuthenticationServiceException failed) {
logger.error(
"An internal error occurred while trying to authenticate the user.",
failed);
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
// Authentication failed
unsuccessfulAuthentication(request, response, failed);
return;
}
// Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
successfulAuthentication(request, response, chain, authResult);
}
authResult = attemptAuthentication(request, response); // 1
抽象方法,ClientCredentialsTokenEndpointFilter实现如下图
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
// 必须为post请求
if (allowOnlyPost && !"POST".equalsIgnoreCase(request.getMethod())) {
throw new HttpRequestMethodNotSupportedException(request.getMethod(), new String[] {
"POST" });
}
String clientId = request.getParameter("client_id");
String clientSecret = request.getParameter("client_secret");
// If the request is already authenticated we can assume that this
// filter is not needed
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
return authentication;
}
if (clientId == null) {
throw new BadCredentialsException("No client credentials presented");
}
if (clientSecret == null) {
clientSecret = "";
}
clientId = clientId.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(clientId,
clientSecret);
return this.getAuthenticationManager().authenticate(authRequest);
}
this.getAuthenticationManager()默认类ProviderManager
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
boolean debug = logger.isDebugEnabled();
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
if (debug) {
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
}
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException e) {
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw e;
}
catch (InternalAuthenticationServiceException e) {
prepareException(e, authentication);
throw e;
}
catch (AuthenticationException e) {
lastException = e;
}
}
if (result == null && parent != null) {
// Allow the parent to try.
try {
result = parent.authenticate(authentication);
}
catch (ProviderNotFoundException e) {
// ignore as we will throw below if no other exception occurred prior to
// calling parent and the parent
// may throw ProviderNotFound even though a provider in the child already
// handled the request
}
catch (AuthenticationException e) {
lastException = e;
}
}
if (result != null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
((CredentialsContainer) result).eraseCredentials();
}
eventPublisher.publishAuthenticationSuccess(result);
return result;
}
// Parent was null, or didn't authenticate (or throw an exception).
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage(
"ProviderManager.providerNotFound",
new Object[] {
toTest.getName() },
"No AuthenticationProvider found for {0}"));
}
prepareException(lastException, authentication);
throw lastException;
}
result = provider.authenticate(authentication);
默认provider为DaoAuthenticationProvider,继承AbstractUserDetailsAuthenticationProvider,先进入父类authenticate:
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported"));
// Determine username
String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
: authentication.getName();
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException notFound) {
logger.debug("User '" + username + "' not found");
if (hideUserNotFoundExceptions) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
else {
throw notFound;
}
}
Assert.notNull(user,
"retrieveUser returned null - a violation of the interface contract");
}
try {
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (AuthenticationException exception) {
if (cacheWasUsed) {
// There was a problem, so try again after checking
// we're using latest data (i.e. not from the cache)
cacheWasUsed = false;
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
else {
throw exception;
}
}
postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
return createSuccessAuthentication(principalToReturn, authentication, user);
}
会先从缓存里面取用户信息(如果设置了缓存),如果未设置,默认为NullUserCache,没有任何实现逻辑
public class NullUserCache implements UserCache {
// ~ Methods
// ========================================================================================================
public UserDetails getUserFromCache(String username) {
return null;
}
public void putUserInCache(UserDetails user) {
}
public void removeUserFromCache(String username) {
}
}
主要看user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);
DaoAuthenticationProvider.retrieveUser():
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
throw ex;
}
catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}
客户端模式this.getUserDetailsService()返回ClientDetailsUserDetailsService
public class ClientDetailsUserDetailsService implements UserDetailsService {
private final ClientDetailsService clientDetailsService;
private String emptyPassword = "";
public ClientDetailsUserDetailsService(ClientDetailsService clientDetailsService) {
this.clientDetailsService = clientDetailsService;
}
/**
* @param passwordEncoder the password encoder to set
*/
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
this.emptyPassword = passwordEncoder.encode("");
}
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
ClientDetails clientDetails;
try {
clientDetails = clientDetailsService.loadClientByClientId(username);
} catch (NoSuchClientException e) {
throw new UsernameNotFoundException(e.getMessage(), e);
}
String clientSecret = clientDetails.getClientSecret();
if (clientSecret== null || clientSecret.trim().length()==0) {
clientSecret = emptyPassword;
}
return new User(username, clientSecret, clientDetails.getAuthorities());
}
}
clientDetailsService是之前配置的RedisClientDetailsService,
其中loadUserByUsername,调用自定义客户端验证,返回ClientDetails对象。
回到AbstractUserDetailsAuthenticationProvider的authenticate(Authentication authentication)中preAuthenticationChecks.check(user),简单验证一下user对象是否被锁定、被禁用等。
下一步additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
由子类DaoAuthenticationProvider调用
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
}
在这里匹配传入的client_secret和查询到的ClientDetails对象中的密码是否相同(加密后),匹配成功返回,整个客户端验证就结束了。
2. 获取token
@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!(principal instanceof Authentication)) {
throw new InsufficientAuthenticationException(
"There is no client authentication. Try adding an appropriate authentication filter.");
}
String clientId = getClientId(principal);
ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);
TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
if (clientId != null && !clientId.equals("")) {
// Only validate the client details if a client authenticated during this
// request.
if (!clientId.equals(tokenRequest.getClientId())) {
// double check to make sure that the client ID in the token request is the same as that in the
// authenticated client
throw new InvalidClientException("Given client ID does not match authenticated client");
}
}
if (authenticatedClient != null) {
oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
}
if (!StringUtils.hasText(tokenRequest.getGrantType())) {
throw new InvalidRequestException("Missing grant type");
}
if (tokenRequest.getGrantType().equals("implicit")) {
throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
}
if (isAuthCodeRequest(parameters)) {
// The scope was requested or determined during the authorization step
if (!tokenRequest.getScope().isEmpty()) {
logger.debug("Clearing scope of incoming token request");
tokenRequest.setScope(Collections.<String> emptySet());
}
}
if (isRefreshTokenRequest(parameters)) {
// A refresh token has its own default scopes, so we should ignore any added by the factory here.
tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
}
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
if (token == null) {
throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
}
return getResponse(token);
}
关键代码
在这之前再次对客户端进行验证
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
public class CompositeTokenGranter implements TokenGranter {
private final List<TokenGranter> tokenGranters;
public CompositeTokenGranter(List<TokenGranter> tokenGranters) {
this.tokenGranters = new ArrayList<TokenGranter>(tokenGranters);
}
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
for (TokenGranter granter : tokenGranters) {
OAuth2AccessToken grant = granter.grant(grantType, tokenRequest);
if (grant!=null) {
return grant;
}
}
return null;
}
public void addTokenGranter(TokenGranter tokenGranter) {
if (tokenGranter == null) {
throw new IllegalArgumentException("Token granter is null");
}
tokenGranters.add(tokenGranter);
}
}
这里的tokenGranters为List,维护了授权的五种方式
1.授权码模式
2.刷新token
3.简化模式
4.客户端模式
5.密码模式
grant方法,根据传入的grantType,找到对应的授权处理器,这里是password模式,由ResourceOwnerPasswordTokenGranter处理,父类AbstractTokenGranter来处理
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
if (!this.grantType.equals(grantType)) {
return null;
}
String clientId = tokenRequest.getClientId();
ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
validateGrantType(grantType, client);
logger.debug("Getting access token for: " + clientId);
return getAccessToken(client, tokenRequest);
}
这里又验证一次客户端(目前仅仅一次调用,一共验证了三次客户端,应该是每个环节可能会在其他地方调用)
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));
}
getOAuth2Authentication由ResourceOwnerPasswordTokenGranter类实现
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
String username = parameters.get("username");
String password = parameters.get("password");
// Protect from downstream leaks of password
parameters.remove("password");
Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
try {
userAuth = authenticationManager.authenticate(userAuth);
}
catch (AccountStatusException ase) {
//covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
throw new InvalidGrantException(ase.getMessage());
}
catch (BadCredentialsException e) {
// If the username/password are wrong the spec says we should send 400/invalid grant
throw new InvalidGrantException(e.getMessage());
}
if (userAuth == null || !userAuth.isAuthenticated()) {
throw new InvalidGrantException("Could not authenticate user: " + username);
}
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
authenticationManager.authenticate(userAuth);这里的authenticationManager最终由ProviderManager来处理,与上面的客户端验证使用同一套逻辑判断,上面使用的是clientId作为username,调用的是ClientDetailService,这里传入的username为用户传入的username,调用UserDetailService。
返回结果后,进行创建token的逻辑,DefaultTokenServices
@Transactional
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
OAuth2RefreshToken refreshToken = null;
if (existingAccessToken != null) {
if (existingAccessToken.isExpired()) {
if (existingAccessToken.getRefreshToken() != null) {
refreshToken = existingAccessToken.getRefreshToken();
tokenStore.removeRefreshToken(refreshToken);
}
tokenStore.removeAccessToken(existingAccessToken);
}
else {
tokenStore.storeAccessToken(existingAccessToken, authentication);
return existingAccessToken;
}
}
if (refreshToken == null) {
refreshToken = createRefreshToken(authentication);
}
else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
refreshToken = createRefreshToken(authentication);
}
}
OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
tokenStore.storeAccessToken(accessToken, authentication);
refreshToken = accessToken.getRefreshToken();
if (refreshToken != null) {
tokenStore.storeRefreshToken(refreshToken, authentication);
}
return accessToken;
}
tokenStore.getAccessToken(),自定义tokenStore去获取OAuth2AccessToken,这里实例中从redis中去获取(先MD5加密后去redis匹配key),如果返回为空,tokenStore.storeRefreshToken(),将新生成的token放入缓存;如果不为空,返回token。整个获取token流程就完成了。
3. 访问拦截
- 不带token,访问需要授权认证的资源
FilterChainProxy$VirtualFilterChain.doFilter(ServletRequest, ServletResponse) line: 311
private static class VirtualFilterChain implements FilterChain {
private final FilterChain originalChain;
private final List<Filter> additionalFilters;
private final FirewalledRequest firewalledRequest;
private final int size;
private int currentPosition = 0;
private VirtualFilterChain(FirewalledRequest firewalledRequest,
FilterChain chain, List<Filter> additionalFilters) {
this.originalChain = chain;
this.additionalFilters = additionalFilters;
this.size = additionalFilters.size();
this.firewalledRequest = firewalledRequest;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
同样,也是根据List依次执行过滤器链,currentPosition来记录目前过滤器索引位。
返回结果
{
“error”: “unauthorized”,
“error_description”: “Full authentication is required to access this resource”
}
-
不带token,访问无需验证的资源
拦截过程同上 -
携带token,访问需要验证的资源
OAuth2AuthenticationProcessingFilter,调用到最后的readAccessToken,从缓存中获取 -
携带token,访问@PreAuthorize的资源,通过OAuth2AuthenticationProcessingFilter拦截处理,调用到TokenStore的readAuthentication,取出缓存中OAuth2Authentication对象,最后再在ExpressionUtils的evaluateAsBoolean进行判断是否包含此接口访问权限。