A、从数据库加载Oauth2客户端信息
当数据量多的时候,用户的信息比较多。最好是从数据库中进行获取。来验证用户信息的合法性。
1、 创建数据表并初始化数据
(表名及字段保持固定)
SET NAMES utf8mb4;SET FOREIGN_KEY_CHECKS = 0;-- ------------------------------ 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;-- ------------------------------ Records of oauth_client_details-- ----------------------------BEGIN;INSERT INTO `oauth_client_details` VALUES ('client_lagou123','autodeliver,resume', 'abcxyz', 'all', 'password,refresh_token',NULL, NULL, 7200, 259200, NULL, NULL);COMMIT;SET FOREIGN_KEY_CHECKS = 1;
2、引入JPA配置的依赖
lagou-cloud-oauth-server-9999添加依赖
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.10</version></dependency><!--操作数据库需要事务控制--><dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId></dependency>
3、 配置数据源
对 lagou-cloud-oauth-server 微服务的application.yml进行操作 ,也就是增加datasource这块。下面给出完整的配置。
server:port: 9999Spring:application:name: lagou-cloud-oauth-serverdatasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/oauth2?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=trueusername: rootpassword: 123456druid:initialSize: 10minIdle: 10maxActive: 30maxWait: 50000eureka:client:serviceUrl: # eureka server的路径defaultZone: http://lagoucloudeurekaservera:8761/eureka/,http://lagoucloudeurekaserverb:8762/eureka/ #把 eureka 集群中的所有 url 都填写了进来,也可以只写一台,因为各个 eureka server 可以同步注册表instance:#使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)prefer-ip-address: true#自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddressinstance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
4、认证服务器主配置类改造
在原来的基础上添加数据源的内容
@Autowiredprivate DataSource dataSource;/*** 客户端详情配置,* 比如client_id,secret* 当前这个服务就如同QQ平台,拉勾网作为客户端需要qq平台进行登录授权认证等,提前需要到QQ平台注册,QQ平台会给拉勾网* 颁发client_id等必要参数,表明客户端是谁* @param clients* @throws Exception*/@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {super.configure(clients);// 从数据库中 加载客户端详情 (对象)clients.withClientDetails(createJdbcClientDetailsService());}// 需要传入数据源,而且需要创建好一个固定的表oauth2@Beanpublic JdbcClientDetailsService createJdbcClientDetailsService() {JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);return jdbcClientDetailsService;}
到此,认证服务器从数据库加载Oauth2客户端详情已经完毕
B、从数据库验证用户合法性
用户信息要从数据库进行匹配,而不是从内存进行匹配了。
1、创建users数据表并初始化数据
(表名不需固定),还需要创建users的实体类
SET NAMES utf8mb4;SET FOREIGN_KEY_CHECKS = 0;-- ------------------------------ Table structure for users-- ----------------------------DROP TABLE IF EXISTS `users`;CREATE TABLE `users` (`id` int(11) NOT NULL AUTO_INCREMENT,`username` char(10) DEFAULT NULL,`password` char(100) DEFAULT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;-- ------------------------------ Records of users-- ----------------------------BEGIN;INSERT INTO `users` VALUES (4, 'lagou-user', 'iuxyzds');COMMIT;SET FOREIGN_KEY_CHECKS = 1;
2、针对该表的操作的Dao接口
public interface UsersRepository extends JpaRepository<Users,Long> {Users findByUsername(String username);}
3、开发UserDetailsService接口的实现类
根据用户名从数据库加载用户信息 ,加载出来后,密码匹配就交给框架来完成。
@Servicepublic class JdbcUserDetailsService implements UserDetailsService {@Autowiredprivate UsersRepository usersRepository;/*** 根据username查询出该用户的所有信息,封装成UserDetails类型的对象返回,* 至于密码,框架会自动匹配* @param username* @return* @throws UsernameNotFoundException*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {Users users = usersRepository.findByUsername(username);return new User(users.getUsername(),users.getPassword(),new ArrayList<>());}}
4、将校验配置类改为从数据库匹配
将写好的JdbcUserDetailsService类,在校验配置类进行注入,
通过 SecurityConfiger 类使用自定义的用户详情服务对象 
把从内存进行校验方式,改为通过jdbc数据库方式获取用户信息进行校验。
@Autowiredprivate JdbcUserDetailsService jdbcUserDetailsService;/*** 处理用户名和密码验证事宜* 1)客户端传递username和password参数到认证服务器* 2)一般来说,username和password会存储在数据库中的用户表中* 3)根据用户表中数据,验证当前传递过来的用户信息的合法性*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// 改为jdbc数据库方式进行获取用户信息进行校验(引入dao层,创建jdbc的实现类)auth.userDetailsService(jdbcUserDetailsService).passwordEncoder(passwordEncoder);}
C、通过网关进行路由
打开网关服务,在断言配置哪里添加对认证的路由
D、基于Oauth2的向JWT 令牌存入扩展信息
OAuth2帮我们生成的JWT令牌载荷部分信息有限,关于用户信息只有一个 user_name,有些场景下我们希望放入一些扩展信息项,比如,之前我们经常向 session中存入userId,或者现在我希望在JWT的载荷部分存入当时请求令牌的客户端IP,客户端携带令牌访问资源服务时,可以对比当前请求的客户端真实IP和令牌中 存放的客户端IP是否匹配,不匹配拒绝请求,以此进一步提高安全性。那么如何在 OAuth2环境下向JWT令牌中存如扩展信息?
认证服务器生成JWT令牌时存入扩展信息
(比如clientIp)继承DefaultAccessTokenConverter类,重写convertAccessToken方法存入扩展信息  
在lagou-cloud-oauth-server-9999这个微服务中,
创建 LagouAccessTokenConvertor  继承DefaultAccessTokenConverter
@Componentpublic class LagouAccessTokenConvertor extends DefaultAccessTokenConverter {// 将扩展的信息放入map中@Overridepublic Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {// 获取到request对象HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.getRequestAttributes())).getRequest();// 获取客户端ip(注意:如果是经过代理之后到达当前服务的话,那么这种方式获取的并不是真实的浏览器客户端ip)String remoteAddr = request.getRemoteAddr();Map<String, String> stringMap = (Map<String, String>) super.convertAccessToken(token, authentication);stringMap.put("clientIp",remoteAddr);return stringMap;}}
认证服务器中修改资源配置类
在这个OauthServerConfiger类中,将自定义的转换器对象注入到 jwt令牌中,通过令牌传递。
@Autowiredprivate LagouAccessTokenConvertor lagouAccessTokenConvertor;/*** 返回jwt令牌转换器(帮助我们生成jwt令牌的)* 在这里,我们可以把签名密钥传递进去给转换器对象* @return*/public JwtAccessTokenConverter jwtAccessTokenConverter() {JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();jwtAccessTokenConverter.setSigningKey(sign_key); // 签名密钥jwtAccessTokenConverter.setVerifier(new MacSigner(sign_key)); // 验证时使用的密钥,和签名密钥保持一致// 这里增加拓展信息的配置类jwtAccessTokenConverter.setAccessTokenConverter(lagouAccessTokenConvertor);return jwtAccessTokenConverter;}

资源服务器取出 JWT令牌扩展信息
资源服务器也需要自定义一个转换器类,继承DefaultAccessTokenConverter,重写extractAuthentication提取方法,把载荷信息设置到认证对象的details属性中  
在lagou-service-autodeliver-8096这个微服务中,
创建LagouAccessTokenConvertor类,同样继承DefaultAccessTokenConverter(上面是放入,这里是提取)
@Componentpublic class LagouAccessTokenConvertor extends DefaultAccessTokenConverter {@Overridepublic OAuth2Authentication extractAuthentication(Map<String, ?> map) {OAuth2Authentication oAuth2Authentication = super.extractAuthentication(map);oAuth2Authentication.setDetails(map); // 将map放入认证对象中,认证对象在controller中可以拿到return oAuth2Authentication;}}
资源服务器中修改资源配置类
将自定义的转换器对象注入到资源配置类中
@Autowiredprivate LagouAccessTokenConvertor lagouAccessTokenConvertor;/*** 返回jwt令牌转换器(帮助我们生成jwt令牌的)* 在这里,我们可以把签名密钥传递进去给转换器对象* @return*/public JwtAccessTokenConverter jwtAccessTokenConverter() {JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();jwtAccessTokenConverter.setSigningKey(sign_key); // 签名密钥jwtAccessTokenConverter.setVerifier(new MacSigner(sign_key)); // 验证时使用的密钥,和签名密钥保持一致jwtAccessTokenConverter.setAccessTokenConverter(lagouAccessTokenConvertor);return jwtAccessTokenConverter;}

在Controller类中获取到认证对象
业务类中如Controller类中,可以通过 SecurityContextHolder.getContext().getAuthentication()获取到认证对象,进一步 获取到扩展信息  。
获取到扩展信息后,就可以做其他的处理了,比如根据userId进一步处理,或者根 据clientIp处理,或者其他都是可以的了  
Object details = SecurityContextHolder.getContext().getAuthentication().getDetails();

其他
关于JWT令牌我们需要注意 
1、JWT令牌就是一种可以被验证的数据组织格式,它的玩法很灵活,我们这里是基 于Spring Cloud Oauth2 创建、校验JWT令牌 
2、我们也可以自己写工具类生成、校验JWT令牌 
3、JWT令牌中不要存放过于敏感的信息,因为我们知道拿到令牌后,我们可以解码看到载荷部分的信息 
4、JWT令牌每次请求都会携带,内容过多,会增加网络带宽占用
