上篇【十一】统⼀认证方案 Spring Cloud OAuth2
目录
什么是JWT
JSON Web Token(JWT)是⼀个开放的行业标准(RFC 7519),它定义了⼀种简介的、自包含的协议格式,用于 在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。JWT可以使用HMAC算法或使⽤RSA的公 钥/私钥对来签名,防止被篡改。
JWT令牌结构 JWT
JWT令牌由三部分组成,每部分中间使用点(.)分隔,如:xxxxx.yyyyy.zzzzz
- Header
头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA),例如将上边的内容使用Base64Url编码,得到⼀个字符串就是JWT令牌的第⼀部分。 - Payload
第⼆部分是负载,内容也是⼀个json对象,它是存放有效信息的地⽅,它可以存放jwt提供的现成字段, 如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。 此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。 最后将第⼆部分负载使用Base64Url编码,得到⼀个字符串就是JWT令牌的第⼆部分。 - Signature
第三部分是签名,此部分用于防jwt内容被篡改。 这个部分使用base64url将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用header中声明 签名算法进行签名。
服务改造
- 认证服务器端JWT改造(改造主配置类)
m-cloud-oauth-server-9999
- OauthServerConfiger
private String sign_key="w123";
//***************************************************************省略***************
/**
* Token 存储方式
*
* @return
*/
public TokenStore tokenStore() {
// return new InMemoryTokenStore();
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* Jwt令牌
* @return
*/
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey(sign_key); //签名秘钥
jwtAccessTokenConverter.setVerifier(new MacSigner(sign_key));//对称加密
return jwtAccessTokenConverter;
}
/**
* 获取Token服务对象,描述token有效期等信息
*
* @return
*/
public AuthorizationServerTokenServices authorizationServerTokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
//开启令牌刷新
defaultTokenServices.setSupportRefreshToken(Boolean.TRUE);
//
defaultTokenServices.setTokenStore(tokenStore());
//针对jwt令牌的添加
defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter());
//设置令牌有效时间(一半2小时)
defaultTokenServices.setAccessTokenValiditySeconds(20);
//设置令牌的有效时间
defaultTokenServices.setRefreshTokenValiditySeconds(259200);
return defaultTokenServices;
}
- 资源服务器校验JWT令牌
m-service-autodeliver-8092
- ResourceServerConfiger
@Configuration
public class ResourceServerConfiger extends ResourceServerConfigurerAdapter {
//jwt 签名秘钥
private String sign_key = "w123";
/**
* 用于定义资源服务器向远程认证服务器发起请求,进行token 校验
*
* @param resources
* @throws Exception
*/
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
/* //资源id
resources.resourceId("autodeliver");
//Token 服务对象
RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
//校验端口
remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:9999/oauth/check_token");
//客户端id和客户端密码
remoteTokenServices.setClientId("client_w");
remoteTokenServices.setClientSecret("abcdef");
resources.tokenServices(remoteTokenServices);*/
//TODO JWT 令牌改造
resources
.resourceId("autodeliver")
.tokenStore(tokenStore())
.stateless(true);
}
/**
* 对不同接口认证授权访问
* 场景:一个服务中有很多资源(api接口)
* 某些接口,需要先认证,才能访问
* 某些接口,不需要认证,本来就是对外访问的接口
*
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
//设置session创建策略
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.authorizeRequests()
.antMatchers("/autodeliver/**").authenticated()
.antMatchers("/demo/**").authenticated()
.anyRequest().permitAll();
}
/**
* Token 存储方式
*
* @return
*/
public TokenStore tokenStore() {
// return new InMemoryTokenStore();
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* Jwt令牌
*
* @return
*/
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey(sign_key); //签名秘钥
jwtAccessTokenConverter.setVerifier(new MacSigner(sign_key));//对称加密
return jwtAccessTokenConverter;
}
}
- 测试
- 获取Token
http://localhost:9999/oauth/token?client_secret=abcdef&grant_type=password&username=admin&password=123456&client_id=client_w
- 校验Token
http://localhost:9999/oauth/check_token?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYXV0b2RlbGl2ZXIiXSwiZXhwIjoxNjgxNDY0MDc0LCJ1c2VyX25hbWUiOiJhZG1pbiIsImp0aSI6IjY3ZDQ5YTRmLTBkNWMtNGFkNy05NjAzLTVmMmVkNGFhOTc1MiIsImNsaWVudF9pZCI6ImNsaWVudF93Iiwic2NvcGUiOlsiYWxsIl19.ENCxdiB47dFm2zwnu8Ka8-utWgA3p0wExHgtpf65xgU
- 刷新token
http://localhost:9999/oauth/token?grant_type=refresh_token&client_id=client_w&client_secret=abcdef&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYXV0b2RlbGl2ZXIiXSwidXNlcl9uYW1lIjoiYWRtaW4iLCJzY29wZSI6WyJhbGwiXSwiYXRpIjoiOGY1ZDdmOWYtYWJkYS00ZDQxLTg1YWMtMTMyMTI0ZmI3ZTA2IiwiZXhwIjoxNjgxNzI0MzU4LCJqdGkiOiIyZDM3YWE3ZC05YzNlLTRjZGEtYmU4My00ZjA4ODAwY2I5YjMiLCJjbGllbnRfaWQiOiJjbGllbnRfdyJ9.KovQ88Pvb4WxaixUVzp0Ft0dYhnAelQVaWhihX3ipOQ
4. 访问服务
http://localhost:8092/autodeliver/checkState/1545133?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYXV0b2RlbGl2ZXIiXSwiZXhwIjoxNjgxNDY1MjU0LCJ1c2VyX25hbWUiOiJhZG1pbiIsImp0aSI6ImY3MDBiOTY2LTM1Y2EtNDI4Yy1hZjhjLWIwNTA4OWI1ZTBiYiIsImNsaWVudF9pZCI6ImNsaWVudF93Iiwic2NvcGUiOlsiYWxsIl19.Wr4liMiJabZkwmRRadgB7GFhhSAQaGYAC2UP60AJ560
从数据库加载Oauth2客户端信息
m-cloud-oauth-server-9999
服务改造
- 新建数据库
oauth2
,初始化脚本
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_w123',
'autodeliver,resume', 'abcdef', 'all', 'password,refresh_token', NULL, NULL,
7200, 259200, NULL, NULL);
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
- 配置数据源
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/oauth2?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai
username: root
password: root
druid:
initialSize: 10
minIdle: 10
maxActive: 30
maxWait: 50000
- 依赖
<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>
- 认证服务器主配置类改造
OauthServerConfiger
package com.w.edu.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.jwt.crypto.sign.MacSigner;
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.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
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 javax.sql.DataSource;
/**
* @author Mrwg
* @date 2023/4/13 19:30
* @description
*/
@Configuration
public class OauthServerConfiger extends AuthorizationServerConfigurerAdapter {
private String sign_key="w123";
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private DataSource dataSource;
/**
* 认证服务器最终以api接口方式对外提供服务(生成令牌、校验令牌)
*
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
super.configure(security);
security
//允许客户端表单认证
.allowFormAuthenticationForClients()
//开启端口/oauth/token_key的访问权限
.tokenKeyAccess("permitAll()")
//开启端口/oauth/check_token的访问权限
.checkTokenAccess("permitAll()");
}
/**
* 客户端吧详情配置
*
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
super.configure(clients);
/* //客户端信息存储在什么地方,既可以在内存也可以在数据库里
clients.inMemory()
//添加客户端配置。指定client_id
.withClient("client_w")
//指定客户端密码、安全码
.secret("abcdef")
//指定客户端可访问的资源id 清单
.resourceIds("autodeliver")
//认证类型、客户端令牌颁发模式//认证模式。可以配置多个
.authorizedGrantTypes("password", "refresh_token")
//客户端权限范围,配置all 即可
.scopes("all");*/
clients.withClientDetails(jdbcClientDetailsService());
}
public JdbcClientDetailsService jdbcClientDetailsService(){
JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
return jdbcClientDetailsService;
}
/**
* Token 令牌管理相关
*
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
super.configure(endpoints);
endpoints
//token 存储方式
.tokenStore(tokenStore())
//配置token细节
.tokenServices(authorizationServerTokenServices())
//认证管理器
.authenticationManager(authenticationManager)
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}
/**
* Token 存储方式
*
* @return
*/
public TokenStore tokenStore() {
// return new InMemoryTokenStore();
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* Jwt令牌
* @return
*/
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey(sign_key); //签名秘钥
jwtAccessTokenConverter.setVerifier(new MacSigner(sign_key));//对称加密
return jwtAccessTokenConverter;
}
/**
* 获取Token服务对象,描述token有效期等信息
*
* @return
*/
public AuthorizationServerTokenServices authorizationServerTokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
//开启令牌刷新
defaultTokenServices.setSupportRefreshToken(Boolean.TRUE);
//
defaultTokenServices.setTokenStore(tokenStore());
//针对jwt令牌的添加
defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter());
//设置令牌有效时间(一半2小时)
defaultTokenServices.setAccessTokenValiditySeconds(20);
//设置令牌的有效时间
defaultTokenServices.setRefreshTokenValiditySeconds(259200);
return defaultTokenServices;
}
}
- 重启验证
http://localhost:9999/oauth/token?client_secret=abcdef&grant_type=password&username=admin&password=123456&client_id=client_w123
从数据库验证用户合法性
m-service-common
服务新建pojo
@Data
@Entity
@Table(name = "users")
@Accessors
public class Users {
@Id
private Long id; // 主键
private String username;
private String password;
}
- 改造服务
m-cloud-oauth-server-9999
- 添加依赖
<!--添加对common⼯程的依赖-->
<dependency>
<groupId>org.example</groupId>
<artifactId>m-service-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
- 新建dao
public interface UsersRespostory extends JpaRepository<Users,Long> {
public Users findByUsername(String username);
}
- 新建service
@Service
public class JdbcUserDetailsService implements UserDetailsService {
@Autowired
private UsersRespostory usersRespostory;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Users users = usersRespostory.findByUsername(username);
return new User(users.getUsername(), users.getPassword(), new ArrayList<>());
}
}
- SecurityConfiger 引入JdbcUserDetailsService 并修改configure方法
@Configuration
public class SecurityConfiger extends WebSecurityConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private JdbcUserDetailsService jdbcUserDetailsService;
/**
*注入AuthenticationManager
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
/**
* 处理用户名密码事宜
* 1 客户传递username 和password 参数到认证服务器
* 2一半存储在数据表中
* 3 根据用户传递的数据,验证当前传递过来用户信息的合法性
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 实例化用户对象
// UserDetails user = new User("admin","123456",new ArrayList<>());
// auth.inMemoryAuthentication()
// .withUser(user).passwordEncoder( passwordEncoder());
auth.userDetailsService(jdbcUserDetailsService).passwordEncoder(passwordEncoder);
}
}
- 验证
基于Oauth2的 JWT 令牌信息扩展
- 认证服务–令牌信息添加额外的ip信息
- 改造
m-cloud-oauth-server-9999
添加新的config配置类
@Component
public class MDefaultAccessTokenConverter extends DefaultAccessTokenConverter {
@Override
public Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
//客户端ip
String remoteAddr = request.getRemoteAddr();
Map<String, String> stringMap = (Map<String, String>) super.convertAccessToken(token, authentication);
stringMap.put("clientIp",remoteAddr);
return stringMap;
// return super.convertAccessToken(token, authentication);
}
}
- 修改OauthServerConfiger.java
@Configuration
public class OauthServerConfiger extends AuthorizationServerConfigurerAdapter {
@Autowired
private MDefaultAccessTokenConverter defaultAccessTokenConverter;
//******************************略部分代码*********
/**
* Jwt令牌
* @return
*/
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey(sign_key); //签名秘钥
jwtAccessTokenConverter.setVerifier(new MacSigner(sign_key));//对称加密
//新增
jwtAccessTokenConverter.setAccessTokenConverter(defaultAccessTokenConverter);
return jwtAccessTokenConverter;
}
}
- 重启验证
- 资源服务-获取ip信息
m-service-autodeliver-8092
- 获取ip信息
@Component
public class MDefaultAccessTokenConverter extends DefaultAccessTokenConverter {
//令牌信息提取
@Override
public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
OAuth2Authentication oAuth2Authentication = super.extractAuthentication(map);
oAuth2Authentication.setDetails(map); //放入认证对象中
return oAuth2Authentication;
}
}
@Configuration
public class ResourceServerConfiger extends ResourceServerConfigurerAdapter {
//**************************************略部分代码***************
@Autowired
private MDefaultAccessTokenConverter defaultAccessTokenConverter;
/**
* Jwt令牌
*
* @return
*/
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey(sign_key); //签名秘钥
jwtAccessTokenConverter.setVerifier(new MacSigner(sign_key));//对称加密
//新增
jwtAccessTokenConverter.setAccessTokenConverter(defaultAccessTokenConverter);
return jwtAccessTokenConverter;
}
}
- 起服务验证