SpringSecurityOauth2包含了认证服务器和资源服务器,认证服务器是用来生成令牌(token),资源服务器是验证该请求是否存在令牌,如果存在才能通过,否则不通过。
认证服务器:在类上加上@EnableAuthorizationServer
认证服务器有4中授权模式:
1.用户名和密码模式
2.授权码模式
3.客户端模式
4.简化模式
关于这四种模式具体可以查看Oauth2官网:https://tools.ietf.org/html/rfc6749
资源服务器:@EnableResourceServer
作用:配置授权资源路径,需要访问令牌才能进行请求。在调用资源服务器之前,应通过OAuth 2.0 Client从授权服务器获取访问令牌。
原理图:
请求路径说明:
1./oauth/authorize 发送请求认证
2./oauth/token 获取accesstoken
3.每次请求 带上请求头
源码解释:
//相当于RestController
@FrameworkEndpoint
public class TokenEndpoint extends AbstractEndpoint {
private OAuth2RequestValidator oAuth2RequestValidator = new DefaultOAuth2RequestValidator();
private Set allowedRequestMethods = new HashSet(Arrays.asList(HttpMethod.POST));
//认证请求
@RequestMapping(value = "/oauth/token", method=RequestMethod.GET)
public ResponseEntity getAccessToken(Principal principal, @RequestParam
Map parameters) throws HttpRequestMethodNotSupportedException {
if (!allowedRequestMethods.contains(HttpMethod.GET)) {
throw new HttpRequestMethodNotSupportedException("GET");
}
return postAccessToken(principal, parameters);
}
@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
public ResponseEntity postAccessToken(Principal principal, @RequestParam
Map parameters) throws HttpRequestMethodNotSupportedException {
if (!(principal instanceof Authentication)) {
throw new InsufficientAuthenticationException(
"There is no client authentication. Try adding an appropriate authentication filter.");
}
//获取客户端id
String clientId = getClientId(principal);
//获取第三方应用的配置
ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);
//创建TokenRequest对象
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");
}
}
//检查Scope
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中
tokenRequest.setScope(Collections. emptySet());
}
}
//刷新令牌 重新设置Scope
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)));
}
//通过TokenGranter创建OAuth2AccessToken
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
if (token == null) {
throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
}
return getResponse(token);
}
/**
* @param principal the currently authentication principal
* @return a client id if there is one in the principal
*/
protected String getClientId(Principal principal) {
Authentication client = (Authentication) principal;
if (!client.isAuthenticated()) {
throw new InsufficientAuthenticationException("The client is not authenticated.");
}
String clientId = client.getName();
if (client instanceof OAuth2Authentication) {
// Might be a client and user combined authentication
clientId = ((OAuth2Authentication) client).getOAuth2Request().getClientId();
}
return clientId;
}
。。。。。。。。。。。。。。。。。。。。。
private boolean isRefreshTokenRequest(Map parameters) {
return "refresh_token".equals(parameters.get("grant_type")) && parameters.get("refresh_token") != null;
}
private boolean isAuthCodeRequest(Map parameters) {
return "authorization_code".equals(parameters.get("grant_type")) && parameters.get("code") != null;
}
public void setOAuth2RequestValidator(OAuth2RequestValidator oAuth2RequestValidator) {
this.oAuth2RequestValidator = oAuth2RequestValidator;
}
public void setAllowedRequestMethods(Set allowedRequestMethods) {
this.allowedRequestMethods = allowedRequestMethods;
}
}
//创建TokenRequest对象
public TokenRequest createTokenRequest(Map requestParameters, ClientDetails authenticatedClient) {
//OAuth2Utils.CLIENT_ID= client_id
String clientId = requestParameters.get(OAuth2Utils.CLIENT_ID);
if (clientId == null) {
// if the clientId wasn't passed in in the map, we add pull it from the authenticated client object
clientId = authenticatedClient.getClientId();
}
else {
// otherwise, make sure that they match
if (!clientId.equals(authenticatedClient.getClientId())) {
throw new InvalidClientException("Given client ID does not match authenticated client");
}
}
//OAuth2Utils.GRANT_TYPE=grant_type 授权类型
String grantType = requestParameters.get(OAuth2Utils.GRANT_TYPE);
//请求范围 创建方位 通过获取
//Set authorities = AuthorityUtils.authorityListToSet(securityContextAccessor.getAuthorities());
Set scopes = extractScopes(requestParameters, clientId);
//new TokenRequest
TokenRequest tokenRequest = new TokenRequest(requestParameters, clientId, scopes, grantType);
return tokenRequest;
}
//通过TokenGranter创建OAuth2AccessToken
public class CompositeTokenGranter implements TokenGranter {
private final List tokenGranters;
public CompositeTokenGranter(List tokenGranters) {
this.tokenGranters = new ArrayList(tokenGranters);
}
//创建OAuth2AccessToken
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);
}
}
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) {
//tokenServices 抽象类 AuthorizationServerTokenServices
return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));
}
//getOAuth2Authentication方法 ResourceOwnerPasswordTokenGranter 密码模式的实现方式
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map parameters = new LinkedHashMap(tokenRequest.getRequestParameters());
String username = parameters.get("username");
String password = parameters.get("password");
// Protect from downstream leaks of password
parameters.remove("password");
//创建 UsernamePasswordAuthenticationToken
Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
try {
//传递给authenticationManager做认证 认证过程中会调用 自己定义的UserDetailsService 验证用户名和密码
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对象
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
//创建 OAuth2Authentication
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
// 将客户端信息和 tokenRequest进行合并
public OAuth2Request createOAuth2Request(ClientDetails client) {
Map requestParameters = getRequestParameters();
HashMap modifiable = new HashMap(requestParameters);
// Remove password if present to prevent leaks
modifiable.remove("password");
modifiable.remove("client_secret");
// Add grant type so it can be retrieved from OAuth2Request
modifiable.put("grant_type", grantType);
return new OAuth2Request(modifiable, client.getClientId(), client.getAuthorities(), true, this.getScope(),
client.getResourceIds(), null, null, null);
}
//创建OAuth2AccessToken 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 {
// Re-store the access token in case the authentication has changed
//重新设置令牌 同一个模式
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);
}
}
//通过 authentication refreshToken创建令牌
OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
tokenStore.storeAccessToken(accessToken, authentication);
// In case it was modified
refreshToken = accessToken.getRefreshToken();
if (refreshToken != null) {
tokenStore.storeRefreshToken(refreshToken, authentication);
}
return accessToken;
}
//通过 authentication refreshToken创建令牌
private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
//UUID
DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());
int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request());
if (validitySeconds > 0) {
//设置过期时间
token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
}
//刷新令牌
token.setRefreshToken(refreshToken);
token.setScope(authentication.getOAuth2Request().getScope());
// accessTokenEnhancer.enhance(token, authentication) 增强器 可以自定义数据到token中
return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;
}
认证模式:
流程说明:
TokenStore通常情况为自定义实现,一般放置在缓存或者数据库中。此处可以利用自定义tokenStore来实现多种需求,如:
同已用户每次获取token,获取到的都是同一个token,只有token失效后才会获取新token。
同一用户每次获取token都生成一个完成周期的token并且保证每次生成的token都能够使用(多点登录)。
同一用户每次获取token都保证只有最后一个token能够使用,之前的token都设为无效(单点token)。
案例:
设置请求头:
采用密码模式(一步请求就可以获取令牌),授权码模式需要两步请求:第一步需要获取code,第二部通过code获取令牌。
密码模式:
http://127.0.0.1:8888/oauth/token?grant_type=password&username=admin&password=123456&scope=all
授权码模式:注意在自定义的UserDetailsService中loadUserByUsername方法返回的UserDetails对象一定要有ROLE_USER这个权限不然的会报错。没有权限访问。
/**
* @author
*使用权限模块的时候需要将该类从spring容器中移除
*
*用户认证
*/
@Component
@Transactional
public class SpringUserDetailsService implements UserDetailsService, SocialUserDetailsService {
private Logger logger = LoggerFactory.getLogger(getClass());
/**
* 密码加密工具类
*/
@Autowired
private PasswordEncoder passwordEncoder;
/*
* (non-Javadoc)
* 表单登陆
* @see org.springframework.security.core.userdetails.UserDetailsService#
* loadUserByUsername(java.lang.String)
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//TODO 查询数据库 封装一个 UserDetails的实现类
return buildUser(username);
}
/**
* 社交登陆
*/
@Override
public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException {
logger.info("设计登录用户Id:" + userId);
return buildUser(userId);
}
private SocialUserDetails buildUser(String userId) {
// 根据用户名查找用户信息
//根据查找到的用户信息判断用户是否被冻结
String password = passwordEncoder.encode("123456");
logger.info("数据库密码是:"+password);
return new SocialUser(userId, password,
true, true, true, true,
// 如果引入了Spring Security OAuth 必须有ROLE_USER这个权限才能访问
//认证服务器,需要添加ROLE_USER角色才会授权
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
}
}
1.调用 /oauth/authorize获取code
2.调用 /oauth/token 获取令牌