在上一节咱们讲述的配置是把受权码存储在redis中,把相应的请求的路径用使用in-memory存储 ,这个是放在了内存中,可是实际开发咱们的数据但愿是从数据表中查询的,那应该怎么作呢?java
/** * inMemory是存储到内存中 并未到数据库 */ clients.inMemory() //client Id .withClient("normal-app") .authorizedGrantTypes("authorization_code", "implicit") .authorities("ROLE_CLIENT") .scopes("read","write") .resourceIds(resourceId) .accessTokenValiditySeconds(accessTokenValiditySeconds)//受权码存活时间 .and() .withClient("trusted-app") .authorizedGrantTypes("client_credentials", "password") .authorities("ROLE_TRUSTED_CLIENT") .scopes("read", "write") .resourceIds(resourceId) .accessTokenValiditySeconds(accessTokenValiditySeconds) .secret("secret");
若是使用的是这种方式,咱们对应的受权码的请求路径以下:web
http://localhost:8787/oauth/authorize?client_id=normal-app&response_type=code&scope=read&redirect_uri=/resources/user
相应的参数请对照上redis
而后咱们使用的是jwt的令牌方式,相应的请求路径以下:算法
http://localhost:8787/oauth/token?code=r8YBUL&grant_type=authorization_code&client_id=normal-app&redirect_uri=/resources/user
这个是放在内存中的存储方式spring
@Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { //默认值InMemoryTokenStore对于单个服务器是彻底正常的(即,在发生故障的状况下,低流量和热备份备份服务器)。大多数项目能够从这里开始,也能够在开发模式下运行,以便轻松启动没有依赖关系的服务器。 //这JdbcTokenStore是同一件事的JDBC版本,它将令牌数据存储在关系数据库中。若是您能够在服务器之间共享数据库,则可使用JDBC版本,若是只有一个,则扩展同一服务器的实例,或者若是有多个组件,则受权和资源服务器。要使用JdbcTokenStore你须要“spring-jdbc”的类路径。 //这个地方指的是从jdbc查出数据来存储 clients.withClientDetails(clientDetails()); }
这里能够看到咱们是把以前的从内存读取的方式给去掉了,取而代之的是clientDetails()这个方法,而后咱们看下这个方法:sql
@Bean public ClientDetailsService clientDetails() { return new JdbcClientDetailsService(dataSource); }
只需配置这个bean便可 可是咱们的datasource是在yml配置文件中配置好了的,只须要注入:数据库
import javax.sql.DataSource; @Resource private DataSource dataSource;
可是这里还没完,咱们首先要讲下JdbcClientDetailsService是如何从数据库读取的,咱们能够点击进入查看相应的源码,以下所示:json
public JdbcClientDetailsService(DataSource dataSource) { this.updateClientDetailsSql = DEFAULT_UPDATE_STATEMENT; this.updateClientSecretSql = "update oauth_client_details set client_secret = ? where client_id = ?"; this.insertClientDetailsSql = "insert into oauth_client_details (client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove, client_id) values (?,?,?,?,?,?,?,?,?,?,?)"; this.selectClientDetailsSql = "select client_id, client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove from oauth_client_details where client_id = ?"; this.passwordEncoder = NoOpPasswordEncoder.getInstance(); Assert.notNull(dataSource, "DataSource required"); this.jdbcTemplate = new JdbcTemplate(dataSource); this.listFactory = new DefaultJdbcListFactory(new NamedParameterJdbcTemplate(this.jdbcTemplate)); }
咱们能够看到,他本身是有一个默认的字段的表的,里面有相应的查询的方法,因此咱们须要创建一个这样的表,sql以下:安全
-- ---------------------------- -- Table structure for oauth_client_details 将请求的路径存在数据表 -- ---------------------------- DROP TABLE IF EXISTS `oauth_client_details`; CREATE TABLE `oauth_client_details` ( `client_id` varchar(48) NOT NULL, `resource_ids` varchar(256) DEFAULT NULL, `client_secret` varchar(256) DEFAULT NULL, `scope` varchar(256) DEFAULT NULL, `authorized_grant_types` varchar(256) DEFAULT NULL, `web_server_redirect_uri` varchar(256) DEFAULT NULL, `authorities` varchar(256) DEFAULT NULL, `access_token_validity` int(11) DEFAULT NULL, `refresh_token_validity` int(11) DEFAULT NULL, `additional_information` varchar(4096) DEFAULT NULL, `autoapprove` varchar(256) DEFAULT NULL, PRIMARY KEY (`client_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
这个是默认的类的表,通常用它默认的便可,咱们这边就须要根据以上的字段配置相关的内容,以下:服务器
这里配置好了以后咱们的访问路径为:
//步骤:客户端向认证服务器申请令牌 http://localhost:8787/oauth/token?client_id=normal-app&grant_type=authorization_code&code=1oCj8e&redirect_uri=http://localhost:8787/resources/user
而后令牌的访问路径为:
//拿到令牌后访问资源: http://localhost:8787/resources/user?access_token=9d62c7b0-780e-4c6a-ad5a-56d79a089342
记得code要换成上一步生成的code
以前咱们用的jwt来存储令牌token,后来我发现怎么也不出现jwttoken,通过屡次检查发现了错误,代码以下:
package urity.demo.oauth2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; 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.ClientDetailsService; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; import org.springframework.security.oauth2.provider.token.AccessTokenConverter; import org.springframework.security.oauth2.provider.token.DefaultTokenServices; import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.core.userdetails.User; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; import urity.demo.service.RedisAuthenticationCodeServices; import urity.demo.support.MyUserDetailService; import javax.annotation.Resource; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; @Configuration @EnableAuthorizationServer public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { @Resource private DataSource dataSource; @Value("${resource.id:spring-boot-application}")//默认是spring-boot-application private String resourceId; @Value("${access_token.validity_period:36000}") private int accessTokenValiditySeconds = 36000; //认证管理 很重要 若是security版本高可能会出坑哦 @Resource private AuthenticationManager authenticationManager; @Resource private RedisAuthenticationCodeServices redisAuthenticationCodeServices; @Resource private MyUserDetailService myUserDetailService; //security //定义令牌端点上的安全约束。 @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')"); oauthServer.checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')"); } //这个是定义受权的请求的路径的Bean @Bean public ClientDetailsService clientDetails() { return new JdbcClientDetailsService(dataSource); } // @Bean // 声明TokenStore实现 // public JdbcTokenStore jdbcTokenStore() { // return new JdbcTokenStore(dataSource); // } //将ClientDetailsServiceConfigurer(从您的回调AuthorizationServerConfigurer)能够用来在内存或JDBC实现客户的细节服务来定义的。客户端的重要属性是 //clientId:(必填)客户端ID。 //secret:(可信客户端须要)客户机密码(若是有)。没有可不填 //scope:客户受限的范围。若是范围未定义或为空(默认值),客户端不受范围限制。read write all //authorizedGrantTypes:授予客户端使用受权的类型。默认值为空。 //authorities授予客户的受权机构(普通的Spring Security权威机构)。 //客户端的详细信息能够经过直接访问底层商店(例如,在数据库表中JdbcClientDetailsService)或经过ClientDetailsManager接口(这两种实现ClientDetailsService也实现)来更新运行的应用程序。 //注意:JDBC服务的架构未与库一块儿打包(由于在实践中可能须要使用太多变体) @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { //默认值InMemoryTokenStore对于单个服务器是彻底正常的(即,在发生故障的状况下,低流量和热备份备份服务器)。大多数项目能够从这里开始,也能够在开发模式下运行,以便轻松启动没有依赖关系的服务器。 //这JdbcTokenStore是同一件事的JDBC版本,它将令牌数据存储在关系数据库中。若是您能够在服务器之间共享数据库,则可使用JDBC版本,若是只有一个,则扩展同一服务器的实例,或者若是有多个组件,则受权和资源服务器。要使用JdbcTokenStore你须要“spring-jdbc”的类路径。 // /** // * inMemory是存储到内存中 并未到数据库 // */ // clients.inMemory() // //client Id // .withClient("normal-app") // .authorizedGrantTypes("authorization_code", "implicit") // .authorities("ROLE_CLIENT") // .scopes("read","write") // .resourceIds(resourceId) // .accessTokenValiditySeconds(accessTokenValiditySeconds)//受权码存活时间 // .and() // .withClient("trusted-app") // .authorizedGrantTypes("client_credentials", "password") // .authorities("ROLE_TRUSTED_CLIENT") // .scopes("read", "write") // .resourceIds(resourceId) // .accessTokenValiditySeconds(accessTokenValiditySeconds) // .secret("secret"); //这个地方指的是从jdbc查出数据来存储 clients.withClientDetails(clientDetails()); } //AuthorizationEndpoint能够经过如下方式配置支持的受权类型AuthorizationServerEndpointsConfigurer。默认状况下,全部受权类型均受支持,除了密码(有关如何切换它的详细信息,请参见下文)。如下属性会影响受权类型: //authenticationManager:经过注入密码受权被打开AuthenticationManager。 //userDetailsService:若是您注入UserDetailsService或者全局配置(例如a GlobalAuthenticationManagerConfigurer),则刷新令牌受权将包含对用户详细信息的检查,以确保该账户仍然活动 //authorizationCodeServices:定义AuthorizationCodeServices受权代码受权的受权代码服务(实例)。 //implicitGrantService:在批准期间管理状态。 //tokenGranter:(TokenGranter彻底控制授予和忽略上述其余属性) //在XML授予类型中包含做为子元素authorization-server。 /** * /oauth/authorize您能够从该请求中获取全部数据, * 而后根据须要进行渲染, * 而后全部用户须要执行的操做都是回复有关批准或拒绝受权的信息。 * 请求参数直接传递给您UserApprovalHandler, * AuthorizationEndpoint因此您能够随便解释数据 * * @param endpoints * @throws Exception */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(this.authenticationManager); endpoints.accessTokenConverter(accessTokenConverter());//jwt //从数据库查请求的路径 // endpoints.tokenStore(jdbcTokenStore()); //从jwt来数据 endpoints.tokenStore(jwtStore()); //受权码存储 endpoints.authorizationCodeServices(redisAuthenticationCodeServices); endpoints.userDetailsService(myUserDetailService); // 配置TokenServices参数 注意这个是默认的uuid的存储设置 与jwt无关 若是要用jwt请注释掉 // DefaultTokenServices tokenServices = new DefaultTokenServices(); //获取令牌的是否从jdbc查 显然 这里是的 // tokenServices.setTokenStore(endpoints.getTokenStore()); //咱们能够用jwt来存放token // tokenServices.setTokenStore(jwtStore()); // tokenServices.setSupportRefreshToken(false); // tokenServices.setClientDetailsService(endpoints.getClientDetailsService()); // tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer()); // tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30)); // 30天 // endpoints.tokenServices(tokenServices); // @Bean // RedisTokenStore redisTokenStore(){ // return new RedisTokenStore(redisConnectionFactory); // } // endpoints.tokenStore(redisTokenStore()); } //定义jwttoken的某些属性 @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter() { /** * 重写加强token的方法 * 自定义返回相应的信息 * */ @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { String userName = authentication.getUserAuthentication().getName(); // 与登陆时候放进去的UserDetail实现类一直查看link{SecurityConfiguration} User user = (User) authentication.getUserAuthentication().getPrincipal(); /** 自定义一些token属性 ***/ final Map<String, Object> additionalInformation = new HashMap<>(); additionalInformation.put("userName", userName); additionalInformation.put("roles", user.getAuthorities()); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation); OAuth2AccessToken enhancedToken = super.enhance(accessToken, authentication); return enhancedToken; } }; // 测试用,资源服务使用相同的字符达到一个对称加密的效果,生产时候使用RSA非对称加密方式 accessTokenConverter.setSigningKey("123"); return accessTokenConverter; } @Bean public TokenStore jwtStore() { TokenStore tokenStore = new JwtTokenStore(accessTokenConverter()); return tokenStore; } /** * 建立一个默认的资源服务token * * @return */ @Bean public ResourceServerTokenServices defaultTokenServices() { final DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); defaultTokenServices.setTokenEnhancer(accessTokenConverter()); defaultTokenServices.setTokenStore(jwtStore()); return defaultTokenServices; } }
请注意:DefaultTokenServices与jwttoken的配置不能都存在,不然系统只找DefaultTokenServices的配置, 也就是生成的token会一直是默认的UUID,这里咱们只能二者选其一配置在代码中
/ 配置TokenServices参数 注意这个是默认的uuid的存储设置 与jwt无关 若是要用jwt请注释掉 // DefaultTokenServices tokenServices = new DefaultTokenServices(); //获取令牌的是否从jdbc查 显然 这里是的 // tokenServices.setTokenStore(endpoints.getTokenStore()); //咱们能够用jwt来存放token // tokenServices.setTokenStore(jwtStore()); // tokenServices.setSupportRefreshToken(false); // tokenServices.setClientDetailsService(endpoints.getClientDetailsService()); // tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer()); // tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30)); // 30天 // endpoints.tokenServices(tokenServices);
咱们从路径受权后得到了code,就能够用code请求相应对的路径换取jwttoken,咱们用postman来进行测试:
这里咱们用basic Auth的方式 只须要填写name:normal-app便可,密码能够不填
http://localhost:8787/oauth/token?client_id=normal-app&grant_type=authorization_code&code=csTjhK&redirect_uri=http://localhost:8787/resources/user
咱们能够看下得到的json:
这里生成的jwttoken中携带了相应这个是jwt的信息,这个一段字符串其实是Header和Payload加密后拼接而成的,相应的能够查看下一篇jwt的相关解析.
咱们能够访问:https://www.jsonwebtoken.io/ 来解析下这个token里面的信息:
这里能够看到Header和Payload的信息,Header主要存储的是type和加密算法,这里是HS256,咱们主要看Payload的信息:
{ "aud": [ "resourceId" ], "user_name": "test", "scope": [ "read" ], "roles": [ { "authority": "ROLE_USER" }, { "authority": "admin" } ], "exp": 1532662701, "userName": "test", "authorities": [ "admin", "ROLE_USER" ], "jti": "066cefa0-0a7a-40da-87a0-133c5a9c64d3", "client_id": "normal-app", "iat": 1532659101 }
这里能够看到登陆的用户名,token的生命周期等.咱们就能够更清晰了解生成的jwttoken携带的信息有哪些了.