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: 9999
Spring:
application:
name: lagou-cloud-oauth-server
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/oauth2?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
username: root
password: 123456
druid:
initialSize: 10
minIdle: 10
maxActive: 30
maxWait: 50000
eureka:
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,早期版本是ipAddress
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
4、认证服务器主配置类改造
在原来的基础上添加数据源的内容
@Autowired
private DataSource dataSource;
/**
* 客户端详情配置,
* 比如client_id,secret
* 当前这个服务就如同QQ平台,拉勾网作为客户端需要qq平台进行登录授权认证等,提前需要到QQ平台注册,QQ平台会给拉勾网
* 颁发client_id等必要参数,表明客户端是谁
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
super.configure(clients);
// 从数据库中 加载客户端详情 (对象)
clients.withClientDetails(createJdbcClientDetailsService());
}
// 需要传入数据源,而且需要创建好一个固定的表oauth2
@Bean
public 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接口的实现类
根据用户名从数据库加载用户信息 ,加载出来后,密码匹配就交给框架来完成。
@Service
public class JdbcUserDetailsService implements UserDetailsService {
@Autowired
private UsersRepository usersRepository;
/**
* 根据username查询出该用户的所有信息,封装成UserDetails类型的对象返回,
* 至于密码,框架会自动匹配
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Users users = usersRepository.findByUsername(username);
return new User(users.getUsername(),users.getPassword(),new ArrayList<>());
}
}
4、将校验配置类改为从数据库匹配
将写好的JdbcUserDetailsService类,在校验配置类进行注入,
通过 SecurityConfiger 类使用自定义的用户详情服务对象
把从内存进行校验方式,改为通过jdbc数据库方式获取用户信息进行校验。
@Autowired
private JdbcUserDetailsService jdbcUserDetailsService;
/**
* 处理用户名和密码验证事宜
* 1)客户端传递username和password参数到认证服务器
* 2)一般来说,username和password会存储在数据库中的用户表中
* 3)根据用户表中数据,验证当前传递过来的用户信息的合法性
*/
@Override
protected 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
@Component
public class LagouAccessTokenConvertor extends DefaultAccessTokenConverter {
// 将扩展的信息放入map中
@Override
public 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令牌中,通过令牌传递。
@Autowired
private 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(上面是放入,这里是提取)
@Component
public class LagouAccessTokenConvertor extends DefaultAccessTokenConverter {
@Override
public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
OAuth2Authentication oAuth2Authentication = super.extractAuthentication(map);
oAuth2Authentication.setDetails(map); // 将map放入认证对象中,认证对象在controller中可以拿到
return oAuth2Authentication;
}
}
资源服务器中修改资源配置类
将自定义的转换器对象注入到资源配置类中
@Autowired
private 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令牌每次请求都会携带,内容过多,会增加网络带宽占用