1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > Spring Security(十一):授权认证(OAuth2)-授权码模式(authorization_code)

Spring Security(十一):授权认证(OAuth2)-授权码模式(authorization_code)

时间:2023-01-08 05:08:21

相关推荐

Spring Security(十一):授权认证(OAuth2)-授权码模式(authorization_code)

一:简介

简单说,OAuth就是一种授权机制。数据的所有者告诉系统,同意授权第三方应用进入系统,获取部分允许获取的数据。系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用。

令牌(token)与密码(password)的作用是一样的,都可以进入系统,但是有三点差异。

令牌是短期的,到期会自动失效,用户自己无法修改。密码一般长期有效,用户不修改,就不会发生变化。令牌可以被数据所有者撤销,会立即失效。密码一般不允许被他人撤销。令牌有权限范围(scope),密码一般是完整权限。

相关文章

理解OAuth 2.0 /blog//05/oauth_2_0.html

二:代码

1. pom.xml

注意:这里使用的springboot的版本为2.1.5.RELEASE,不同的版本功能实现上可能会有差异。

<?xml version="1.0" encoding="UTF-8"?><project xmlns="/POM/4.0.0" xmlns:xsi="/2001/XMLSchema-instance"xsi:schemaLocation="/POM/4.0.0 /xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.5.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.example</groupId><artifactId>springboot-security-example</artifactId><version>0.0.1-SNAPSHOT</version><name>springboot-security-example</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.security.oauth.boot</groupId><artifactId>spring-security-oauth2-autoconfigure</artifactId><version>2.1.2.RELEASE</version></dependency><dependency><groupId>org.springframework.security.oauth</groupId><artifactId>spring-security-oauth2</artifactId><version>2.3.4.RELEASE</version><scope>compile</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

2. application.yml

注意:在学习Spring的新技术时最好打开debug日志,这样控制台会输出更多有用的信息,可以根据控制台的输出日志来了解该功能使用到的主要的类,涉及到的主要方法等。

application.yml的配置是可选的。因为服务端口默认是8080,日志的配置也不是必须的。

server:port: 8080logging:level:org.springframework: debug

3. UserDetailsService

@Componentpublic class MyUserDetailsService implements UserDetailsService {@Autowiredprivate PasswordEncoder passwordEncoder;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {return new User(username, passwordEncoder.encode("123456"), maSeparatedStringToAuthorityList("admin"));}}

4. SecurityConfiguration

注意:关于密码加密器PasswordEncoder,如果使用加密可以使用BCryptPasswordEncoder,如果不加密可以自己实现一个PasswordEncoder的实现类

@Configuration@EnableWebSecuritypublic class SecurityConfiguration extends WebSecurityConfigurerAdapter {@Autowiredprivate MyUserDetailsService myUserDetailsService;@Overrideprotected void configure(HttpSecurity http) throws Exception {http.formLogin().and().csrf().disable();}@Overridepublic void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder());}@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}// @Bean// public PasswordEncoder passwordEncoder() {// 对原始字符不进行加密,比较时都会返回true// return new PasswordEncoder() {// @Override// public String encode (CharSequence charSequence) {//return charSequence.toString();// }// @Override// public boolean matches(CharSequence charSequence, String s) {//return true;// }// };// }}

5. 认证服务器 AuthorizationServer

注意:对于clients.secret设置,网上大部分都是直接赋值一个明文,究竟设置为明文还是密文取决与SecurityConfiguration类中配置的PasswordEncoder是什么,如果PasswordEncoder为BCryptPasswordEncoder,此时clients.secret也必须设置为BCryptPasswordEncoder加密后的密文,如果PasswordEncoder为上文注释的自定义的密码加密器(该实现任何情况下都会返回true),此时clients.secret可以设置为明文。

@Configuration@EnableAuthorizationServerpublic class AuthServerConfiguration extends AuthorizationServerConfigurerAdapter {@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory() // 使用in-memory存储.withClient("clientId").secret(new BCryptPasswordEncoder().encode("clientSecret")).authorizedGrantTypes("authorization_code").scopes("all").redirectUris("");}@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {security.tokenKeyAccess("permitAll()").checkTokenAccess("permitAll()").allowFormAuthenticationForClients();}}

三:获取access_token

在浏览器访问 localhost:8080/oauth/authorize?client_id=clientId&response_type=code&redirect_uri= 会跳转到http://localhost:8080/login,输入用户名这里输入的是admin(其实用户名可以输入任何字符),密码123456(密码必须和UserDetailsService配置的用户密码保持一致)

登录成功后跳转到授权页面,选择同意Approve并点击授权Authorize

授权成功后会跳转到AuthorizationServer配置的redirectUris上,这里配置的是百度,会跳转到并携带一个code参数

根据上个步骤获取到的code值,然后获取access_token

4.1 使用POST请求方式访问 http://localhost:8080/oauth/token

4.2 设置请求头Authorization,Username设置为我们设置的clientId,Password为我们设置的clientSecret

4.3 参数 grant_type的值固定为authorization_code,code 为上个步骤获取到的值,其它值都是认证服务器配置的值

这里使用了一个Chrome的发请求的客户端插件 Restlet Client - REST API Testing ,如果没有可以使用Postman来代替

四:/oauth/token源码分析

TokenEndpoint: token结束点,可以理解为Token Controller,用于接收"/oauth/token"请求InMemoryClientDetailsService implements ClientDetailsService: 根据clientId读取第三方应用的配置信息(ClientDetails)TokenRequest: 封装"/oauth/token"中请求的参数和ClientDetailsCompositeTokenGranter implements TokenGranter OAuth2Request: 组装 ClientDetails 和 TokenRequest对象Authentication: 授权用户信息, 从UserDeatails中获取的OAuth2Authentication: 组装OAuth2Request和Authentication对象 DefaultTokenServices implements AuthorizationServerTokenServices: 生成令牌 OAuth2AccessToken InMemoryTokenStore implements TokenStore: 令牌的存取和删除(TokenStore的实现类有InMemoryTokenStore、JdbcTokenStore、JwtTokenStore、JwkTokenStore、RedisTokenStore)TokenEnhancer: 用于改造令牌(JwtAccessTokenConverter)

@FrameworkEndpointpublic class TokenEndpoint extends AbstractEndpoint {@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParamMap<String, String> parameters) throws HttpRequestMethodNotSupportedException {if (!(principal instanceof Authentication)) {throw new InsufficientAuthenticationException("There is no client authentication. Try adding an appropriate authentication filter.");}// 获取clientIdString clientId = getClientId(principal);// 获取第三方应用的配置信息ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);// 组装parameters和authenticatedClientTokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);if (clientId != null && !clientId.equals("")) {if (!clientId.equals(tokenRequest.getClientId())) {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)) {if (!tokenRequest.getScope().isEmpty()) {logger.debug("Clearing scope of incoming token request");tokenRequest.setScope(Collections.<String> emptySet());}}if (isRefreshTokenRequest(parameters)) {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);}}

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) {// authorization_code -> AuthorizationCodeTokenGranterOAuth2AccessToken grant = granter.grant(grantType, tokenRequest);if (grant!=null) {return grant;}}return null;}}

public class AuthorizationCodeTokenGranter extends AbstractTokenGranter {@Overrideprotected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {Map<String, String> parameters = tokenRequest.getRequestParameters();String authorizationCode = parameters.get("code");String redirectUri = parameters.get(OAuth2Utils.REDIRECT_URI);if (authorizationCode == null) {throw new InvalidRequestException("An authorization code must be supplied.");}OAuth2Authentication storedAuth = authorizationCodeServices.consumeAuthorizationCode(authorizationCode);if (storedAuth == null) {throw new InvalidGrantException("Invalid authorization code: " + authorizationCode);}OAuth2Request pendingOAuth2Request = storedAuth.getOAuth2Request();String redirectUriApprovalParameter = pendingOAuth2Request.getRequestParameters().get(OAuth2Utils.REDIRECT_URI);if ((redirectUri != null || redirectUriApprovalParameter != null)&& !pendingOAuth2Request.getRedirectUri().equals(redirectUri)) {throw new RedirectMismatchException("Redirect URI mismatch.");}String pendingClientId = pendingOAuth2Request.getClientId();String clientId = tokenRequest.getClientId();if (clientId != null && !clientId.equals(pendingClientId)) {// just a sanity check.throw new InvalidClientException("Client ID mismatch");}Map<String, String> combinedParameters = new HashMap<String, String>(pendingOAuth2Request.getRequestParameters());combinedParameters.putAll(parameters);OAuth2Request finalStoredOAuth2Request = pendingOAuth2Request.createOAuth2Request(combinedParameters);Authentication userAuth = storedAuth.getUserAuthentication();return new OAuth2Authentication(finalStoredOAuth2Request, userAuth);}}

public abstract class AbstractTokenGranter implements 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);if (logger.isDebugEnabled()) {logger.debug("Getting access token for: " + clientId);}return getAccessToken(client, tokenRequest);}/*** 将 ClientDetails 和 TokenRequest组装成OAuth2AccessToken**/protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));}}

public class DefaultTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,ConsumerTokenServices, InitializingBean {@Transactionalpublic 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) {// 创建refreshTokenrefreshToken = createRefreshToken(authentication);}else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {refreshToken = createRefreshToken(authentication);}}// 创建access_tokenOAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);tokenStore.storeAccessToken(accessToken, authentication);refreshToken = accessToken.getRefreshToken();if (refreshToken != null) {tokenStore.storeRefreshToken(refreshToken, authentication);}return accessToken;}private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {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());return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;}}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。