A、从数据库加载Oauth2客户端信息

当数据量多的时候,用户的信息比较多。最好是从数据库中进行获取。来验证用户信息的合法性。

1、 创建数据表并初始化数据

(表名及字段保持固定)

  1. SET NAMES utf8mb4;
  2. SET FOREIGN_KEY_CHECKS = 0;
  3. -- ----------------------------
  4. -- Table structure for oauth_client_details
  5. -- ----------------------------
  6. DROP TABLE IF EXISTS `oauth_client_details`;
  7. CREATE TABLE `oauth_client_details` (
  8. `client_id` varchar(48) NOT NULL,
  9. `resource_ids` varchar(256) DEFAULT NULL,
  10. `client_secret` varchar(256) DEFAULT NULL,
  11. `scope` varchar(256) DEFAULT NULL,
  12. `authorized_grant_types` varchar(256) DEFAULT NULL,
  13. `web_server_redirect_uri` varchar(256) DEFAULT NULL,
  14. `authorities` varchar(256) DEFAULT NULL,
  15. `access_token_validity` int(11) DEFAULT NULL,
  16. `refresh_token_validity` int(11) DEFAULT NULL,
  17. `additional_information` varchar(4096) DEFAULT NULL,
  18. `autoapprove` varchar(256) DEFAULT NULL,
  19. PRIMARY KEY (`client_id`)
  20. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  21. -- ----------------------------
  22. -- Records of oauth_client_details
  23. -- ----------------------------
  24. BEGIN;
  25. INSERT INTO `oauth_client_details` VALUES ('client_lagou123',
  26. 'autodeliver,resume', 'abcxyz', 'all', 'password,refresh_token',
  27. NULL, NULL, 7200, 259200, NULL, NULL);
  28. COMMIT;
  29. SET FOREIGN_KEY_CHECKS = 1;

2、引入JPA配置的依赖

lagou-cloud-oauth-server-9999添加依赖

  1. <dependency>
  2. <groupId>mysql</groupId>
  3. <artifactId>mysql-connector-java</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>com.alibaba</groupId>
  7. <artifactId>druid-spring-boot-starter</artifactId>
  8. <version>1.1.10</version>
  9. </dependency>
  10. <!--操作数据库需要事务控制-->
  11. <dependency>
  12. <groupId>org.springframework</groupId>
  13. <artifactId>spring-tx</artifactId>
  14. </dependency>
  15. <dependency>
  16. <groupId>org.springframework</groupId>
  17. <artifactId>spring-jdbc</artifactId>
  18. </dependency>

3、 配置数据源

对 lagou-cloud-oauth-server 微服务的application.yml进行操作 ,也就是增加datasource这块。下面给出完整的配置。

  1. server:
  2. port: 9999
  3. Spring:
  4. application:
  5. name: lagou-cloud-oauth-server
  6. datasource:
  7. driver-class-name: com.mysql.jdbc.Driver
  8. url: jdbc:mysql://localhost:3306/oauth2?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
  9. username: root
  10. password: 123456
  11. druid:
  12. initialSize: 10
  13. minIdle: 10
  14. maxActive: 30
  15. maxWait: 50000
  16. eureka:
  17. client:
  18. serviceUrl: # eureka server的路径
  19. defaultZone: http://lagoucloudeurekaservera:8761/eureka/,http://lagoucloudeurekaserverb:8762/eureka/ #把 eureka 集群中的所有 url 都填写了进来,也可以只写一台,因为各个 eureka server 可以同步注册表
  20. instance:
  21. #使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
  22. prefer-ip-address: true
  23. #自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
  24. instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@

4、认证服务器主配置类改造

在原来的基础上添加数据源的内容

  1. @Autowired
  2. private DataSource dataSource;
  3. /**
  4. * 客户端详情配置,
  5. * 比如client_id,secret
  6. * 当前这个服务就如同QQ平台,拉勾网作为客户端需要qq平台进行登录授权认证等,提前需要到QQ平台注册,QQ平台会给拉勾网
  7. * 颁发client_id等必要参数,表明客户端是谁
  8. * @param clients
  9. * @throws Exception
  10. */
  11. @Override
  12. public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
  13. super.configure(clients);
  14. // 从数据库中 加载客户端详情 (对象)
  15. clients.withClientDetails(createJdbcClientDetailsService());
  16. }
  17. // 需要传入数据源,而且需要创建好一个固定的表oauth2
  18. @Bean
  19. public JdbcClientDetailsService createJdbcClientDetailsService() {
  20. JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
  21. return jdbcClientDetailsService;
  22. }

到此,认证服务器从数据库加载Oauth2客户端详情已经完毕

B、从数据库验证用户合法性

用户信息要从数据库进行匹配,而不是从内存进行匹配了。

1、创建users数据表并初始化数据

(表名不需固定),还需要创建users的实体类

  1. SET NAMES utf8mb4;
  2. SET FOREIGN_KEY_CHECKS = 0;
  3. -- ----------------------------
  4. -- Table structure for users
  5. -- ----------------------------
  6. DROP TABLE IF EXISTS `users`;
  7. CREATE TABLE `users` (
  8. `id` int(11) NOT NULL AUTO_INCREMENT,
  9. `username` char(10) DEFAULT NULL,
  10. `password` char(100) DEFAULT NULL,
  11. PRIMARY KEY (`id`)
  12. ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
  13. -- ----------------------------
  14. -- Records of users
  15. -- ----------------------------
  16. BEGIN;
  17. INSERT INTO `users` VALUES (4, 'lagou-user', 'iuxyzds');
  18. COMMIT;
  19. SET FOREIGN_KEY_CHECKS = 1;

2、针对该表的操作的Dao接口

  1. public interface UsersRepository extends JpaRepository<Users,Long> {
  2. Users findByUsername(String username);
  3. }

3、开发UserDetailsService接口的实现类

根据用户名从数据库加载用户信息 ,加载出来后,密码匹配就交给框架来完成。

  1. @Service
  2. public class JdbcUserDetailsService implements UserDetailsService {
  3. @Autowired
  4. private UsersRepository usersRepository;
  5. /**
  6. * 根据username查询出该用户的所有信息,封装成UserDetails类型的对象返回,
  7. * 至于密码,框架会自动匹配
  8. * @param username
  9. * @return
  10. * @throws UsernameNotFoundException
  11. */
  12. @Override
  13. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  14. Users users = usersRepository.findByUsername(username);
  15. return new User(users.getUsername(),users.getPassword(),new ArrayList<>());
  16. }
  17. }

4、将校验配置类改为从数据库匹配

将写好的JdbcUserDetailsService类,在校验配置类进行注入,
通过 SecurityConfiger 类使用自定义的用户详情服务对象
把从内存进行校验方式,改为通过jdbc数据库方式获取用户信息进行校验
image.png

  1. @Autowired
  2. private JdbcUserDetailsService jdbcUserDetailsService;
  3. /**
  4. * 处理用户名和密码验证事宜
  5. * 1)客户端传递username和password参数到认证服务器
  6. * 2)一般来说,username和password会存储在数据库中的用户表中
  7. * 3)根据用户表中数据,验证当前传递过来的用户信息的合法性
  8. */
  9. @Override
  10. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  11. // 改为jdbc数据库方式进行获取用户信息进行校验(引入dao层,创建jdbc的实现类)
  12. auth.userDetailsService(jdbcUserDetailsService).passwordEncoder(passwordEncoder);
  13. }

C、通过网关进行路由

打开网关服务,在断言配置哪里添加对认证的路由
image.png

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

  1. @Component
  2. public class LagouAccessTokenConvertor extends DefaultAccessTokenConverter {
  3. // 将扩展的信息放入map中
  4. @Override
  5. public Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
  6. // 获取到request对象
  7. HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.getRequestAttributes())).getRequest();
  8. // 获取客户端ip(注意:如果是经过代理之后到达当前服务的话,那么这种方式获取的并不是真实的浏览器客户端ip)
  9. String remoteAddr = request.getRemoteAddr();
  10. Map<String, String> stringMap = (Map<String, String>) super.convertAccessToken(token, authentication);
  11. stringMap.put("clientIp",remoteAddr);
  12. return stringMap;
  13. }
  14. }

认证服务器中修改资源配置类

在这个OauthServerConfiger类中,将自定义的转换器对象注入到 jwt令牌中,通过令牌传递。

  1. @Autowired
  2. private LagouAccessTokenConvertor lagouAccessTokenConvertor;
  3. /**
  4. * 返回jwt令牌转换器(帮助我们生成jwt令牌的)
  5. * 在这里,我们可以把签名密钥传递进去给转换器对象
  6. * @return
  7. */
  8. public JwtAccessTokenConverter jwtAccessTokenConverter() {
  9. JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
  10. jwtAccessTokenConverter.setSigningKey(sign_key); // 签名密钥
  11. jwtAccessTokenConverter.setVerifier(new MacSigner(sign_key)); // 验证时使用的密钥,和签名密钥保持一致
  12. // 这里增加拓展信息的配置类
  13. jwtAccessTokenConverter.setAccessTokenConverter(lagouAccessTokenConvertor);
  14. return jwtAccessTokenConverter;
  15. }

image.png

资源服务器取出 JWT令牌扩展信息

资源服务器也需要自定义一个转换器类,继承DefaultAccessTokenConverter,重写extractAuthentication提取方法,把载荷信息设置到认证对象的details属性中
在lagou-service-autodeliver-8096这个微服务中,
创建LagouAccessTokenConvertor类,同样继承DefaultAccessTokenConverter(上面是放入,这里是提取)

  1. @Component
  2. public class LagouAccessTokenConvertor extends DefaultAccessTokenConverter {
  3. @Override
  4. public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
  5. OAuth2Authentication oAuth2Authentication = super.extractAuthentication(map);
  6. oAuth2Authentication.setDetails(map); // 将map放入认证对象中,认证对象在controller中可以拿到
  7. return oAuth2Authentication;
  8. }
  9. }

资源服务器中修改资源配置类

将自定义的转换器对象注入到资源配置类中

  1. @Autowired
  2. private LagouAccessTokenConvertor lagouAccessTokenConvertor;
  3. /**
  4. * 返回jwt令牌转换器(帮助我们生成jwt令牌的)
  5. * 在这里,我们可以把签名密钥传递进去给转换器对象
  6. * @return
  7. */
  8. public JwtAccessTokenConverter jwtAccessTokenConverter() {
  9. JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
  10. jwtAccessTokenConverter.setSigningKey(sign_key); // 签名密钥
  11. jwtAccessTokenConverter.setVerifier(new MacSigner(sign_key)); // 验证时使用的密钥,和签名密钥保持一致
  12. jwtAccessTokenConverter.setAccessTokenConverter(lagouAccessTokenConvertor);
  13. return jwtAccessTokenConverter;
  14. }

image.png

在Controller类中获取到认证对象

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

  1. Object details = SecurityContextHolder.getContext().getAuthentication().getDetails();

image.png

其他

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