@ TOC
OAuth2.0系列博客:
在 前面文章 中我们学习了OAuth2的一些基本概念,对OAuth2有了基本的认识,也对OAuth2.0的令牌等进行数据库存储,对应博客: jdbc方式的数据存储 ,然后如果不想存储令牌可以实现?
IDEA中,Ctrl+Alt+B,可以看到TokenStore的实现,有如下几种:
ok,其实对于token存储有如上方式,分别进行介绍:
实验环境准备:
主要加入如下配置:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Cloud Oauth2--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <!-- Spring Cloud Security--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-security</artifactId> </dependency> 复制代码
TokenStore:
@Bean public TokenStore jwtTokenStore() { //基于jwt实现令牌(Access Token) return new JwtTokenStore(accessTokenConverter()); } 复制代码
JwtAccessTokenConverter :
@Bean public JwtAccessTokenConverter accessTokenConverter(){ JwtAccessTokenConverter converter = new JwtAccessTokenConverter(){ @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { String grantType = authentication.getOAuth2Request().getGrantType(); //授权码和密码模式才自定义token信息 if(AUTHORIZATION_CODE.equals(grantType) || GRANT_TYPE_PASSWORD.equals(grantType)) { String userName = authentication.getUserAuthentication().getName(); // 自定义一些token 信息 Map<String, Object> additionalInformation = new HashMap<String, Object>(16); additionalInformation.put("user_name", userName); additionalInformation = Collections.unmodifiableMap(additionalInformation); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation); } OAuth2AccessToken token = super.enhance(accessToken, authentication); return token; } }; // 设置签署key converter.setSigningKey("signingKey"); return converter; } 复制代码
配置accessTokenConverter
@Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(jwtTokenStore()).authenticationManager(authenticationManager) //自定义accessTokenConverter .accessTokenConverter(accessTokenConverter()) //支持获取token方式 .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST,HttpMethod.PUT,HttpMethod.DELETE,HttpMethod.OPTIONS); } 复制代码
总的配置类参考:
package com.example.springboot.oauth2.configuration; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * <pre> * OAuth2.0配置类 * </pre> * * <pre> * @author mazq * 修改记录 * 修改后版本: 修改人: 修改日期: 2020/06/17 11:44 修改内容: * </pre> */ @Configuration @EnableAuthorizationServer public class OAuth2Configuration extends AuthorizationServerConfigurerAdapter { private static final String CLIENT_ID = "cms"; private static final String SECRET_CHAR_SEQUENCE = "{noop}secret"; private static final String SCOPE_READ = "read"; private static final String SCOPE_WRITE = "write"; private static final String TRUST = "trust"; private static final String USER ="user"; private static final String ALL = "all"; private static final int ACCESS_TOKEN_VALIDITY_SECONDS = 2*60; private static final int FREFRESH_TOKEN_VALIDITY_SECONDS = 2*60; // 密码模式授权模式 private static final String GRANT_TYPE_PASSWORD = "password"; //授权码模式 private static final String AUTHORIZATION_CODE = "authorization_code"; //refresh token模式 private static final String REFRESH_TOKEN = "refresh_token"; //简化授权模式 private static final String IMPLICIT = "implicit"; //指定哪些资源是需要授权验证的 private static final String RESOURCE_ID = "resource_id"; @Autowired @Qualifier("authenticationManagerBean") private AuthenticationManager authenticationManager; @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients // 使用内存存储 .inMemory() //标记客户端id .withClient(CLIENT_ID) //客户端安全码 .secret(SECRET_CHAR_SEQUENCE) //为true 直接自动授权成功返回code .autoApprove(true) .redirectUris("http://127.0.0.1:8084/cms/login") //重定向uri //允许授权范围 .scopes(ALL) //token 时间秒 .accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS) //刷新token 时间 秒 .refreshTokenValiditySeconds(FREFRESH_TOKEN_VALIDITY_SECONDS) //允许授权类型 .authorizedGrantTypes(GRANT_TYPE_PASSWORD , AUTHORIZATION_CODE , REFRESH_TOKEN , IMPLICIT); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(jwtTokenStore()).authenticationManager(authenticationManager) //自定义accessTokenConverter .accessTokenConverter(accessTokenConverter()) //支持获取token方式 .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST,HttpMethod.PUT,HttpMethod.DELETE,HttpMethod.OPTIONS); } /** * 认证服务器的安全配置 * @param security * @throws Exception */ @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security // 开启/oauth/token_key验证端口认证权限访问 .tokenKeyAccess("isAuthenticated()") // 开启/oauth/check_token验证端口认证权限访问 .checkTokenAccess("isAuthenticated()") //允许表单认证 .allowFormAuthenticationForClients(); } @Bean public JwtAccessTokenConverter accessTokenConverter(){ JwtAccessTokenConverter converter = new JwtAccessTokenConverter(){ @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { String grantType = authentication.getOAuth2Request().getGrantType(); //授权码和密码模式才自定义token信息 if(AUTHORIZATION_CODE.equals(grantType) || GRANT_TYPE_PASSWORD.equals(grantType)) { String userName = authentication.getUserAuthentication().getName(); // 自定义一些token 信息 Map<String, Object> additionalInformation = new HashMap<String, Object>(16); additionalInformation.put("user_name", userName); additionalInformation = Collections.unmodifiableMap(additionalInformation); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation); } OAuth2AccessToken token = super.enhance(accessToken, authentication); return token; } }; // 设置签署key converter.setSigningKey("signingKey"); return converter; } @Bean public TokenStore jwtTokenStore() { //基于jwt实现令牌(Access Token) return new JwtTokenStore(accessTokenConverter()); } } 复制代码
SpringSecurity配置类:
package com.example.springboot.oauth2.configuration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; /** * <pre> * Spring Security配置类 * </pre> * * <pre> * @author mazq * 修改记录 * 修改后版本: 修改人: 修改日期: 2020/06/15 10:39 修改内容: * </pre> */ @Configuration @EnableWebSecurity @Order(1) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //auth.inMemoryAuthentication() auth.inMemoryAuthentication() .withUser("nicky") .password("{noop}123") .roles("admin"); } @Override public void configure(WebSecurity web) throws Exception { //解决静态资源被拦截的问题 web.ignoring().antMatchers("/asserts/**"); web.ignoring().antMatchers("/favicon.ico"); } @Override protected void configure(HttpSecurity http) throws Exception { http // 配置登录页并允许访问 .formLogin().permitAll() // 配置Basic登录 //.and().httpBasic() // 配置登出页面 .and().logout().logoutUrl("/logout").logoutSuccessUrl("/") .and().authorizeRequests().antMatchers("/oauth/**", "/login/**", "/logout/**").permitAll() // 其余所有请求全部需要鉴权认证 .anyRequest().authenticated() // 关闭跨域保护; .and().csrf().disable(); } } 复制代码
访问授权链接,在浏览器访问就可以,授权码模式response_type参数传code: http://localhost:8888/oauth/authorize?client_id=cms&client_secret=secret&response_type=code
因为没登录,所以会返回SpringSecurity的默认登录页面,具体代码是 http .formLogin().permitAll();
,如果要弹窗登录的,可以配置 http.httpBasic();
,这种配置是没有登录页面的,自定义登录页面可以这样配置 http.formLogin().loginPage("/login").permitAll()
如图,输入SpringSecurity配置的数据库密码
登录成功,返回redirect_uri,拿到授权码
重定向回redirect_uri, http://localhost:8084/cms/login?code= ???
配置一下请求头的授权参数,用Basic Auth方式,username即client_id,password即client_secret
拿到授权码之后去获取token,本教程使用授权码方式
JWT方式的token
{ "access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTIzNzYwNjEsInVzZXJfbmFtZSI6Im5pY2t5IiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9hZG1pbiJdLCJqdGkiOiJiM2IwZGExNS1mMmQyLTRlN2MtYTUwNC1iMzg5YjkxMjM0MDMiLCJjbGllbnRfaWQiOiJjbXMiLCJzY29wZSI6WyJhbGwiXX0.TpIBd9Gtb4M7sC1MSQsxsn8mwnhAm59CUBZPU7jwdnE", "token_type":"bearer", "refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJuaWNreSIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiJiM2IwZGExNS1mMmQyLTRlN2MtYTUwNC1iMzg5YjkxMjM0MDMiLCJleHAiOjE1OTIzNzYwNjEsImF1dGhvcml0aWVzIjpbIlJPTEVfYWRtaW4iXSwianRpIjoiODVhYTlmMGYtNDliNS00NDg4LTk4MTQtNmM0MmZjMjZkYTc2IiwiY2xpZW50X2lkIjoiY21zIn0.TU8ZD_5AxRGbgbOWZSuWAxwWjMJ4HLHniA46M-dnChE", "expires_in":119, "scope":"all", "user_name":"nicky", "jti":"b3b0da15-f2d2-4e7c-a504-b389b9123403" } 复制代码
例子代码下载: code download