原文地址:https://blog.lanweihong.com/p...
OAuth 是一种用来标准令牌(Token)发放的受权机制,次要蕴含了四种受权模式:受权码模式、简化模式、明码模式和客户端模式。对于 OAuth 更多介绍可拜访 了解OAuth 2.0 查看。本文次要以 明码模式 来实现用户认证和受权。
搭建我的项目
我的项目代码已上传至 Github 。
本例我的项目以微服务为根底,仅实现认证服务和资源服务,其余如网关、服务治理、配置核心等省略,本文重点是应用 Spring Security + OAuth 2.0 + JWT 实现用户认证受权。
我的项目构造如下图,认证服务和资源服务拆散,认证服务次要是提供令牌和校验令牌服务。
父工程 pom.xml
配置如下,次要是指定依赖包的版本:
<!-- 依赖包版本治理 --> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <spring.boot.version>2.2.2.RELEASE</spring.boot.version> <spring.cloud.version>Hoxton.SR9</spring.cloud.version> <spring.cloud.alibaba.version>2.2.1.RELEASE</spring.cloud.alibaba.version> <mysql.driver.version>8.0.16</mysql.driver.version> <lombok.version>1.16.18</lombok.version> <druid.version>1.1.10</druid.version> </properties> <dependencyManagement> <dependencies> <!-- spring boot 2.2.2.RELEASE --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.2.2.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <!-- spring cloud --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring.cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- Spring Cloud Alibaba --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring.cloud.alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- mysql driver --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.driver.version}</version> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </dependency> <!-- druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>${druid.version}</version> </dependency> </dependencies> </dependencyManagement>
搭建认证服务
引入依赖
- 在
pom.xml
引入以下依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId></dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-jose</artifactId></dependency><dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId></dependency>
spring-cloud-starter-oauth2
曾经蕴含了 spring-cloud-starter-security
、spring-security-oauth2
、spring-security-jwt
这3个依赖,只需引入 spring-cloud-starter-oauth2
即可。
- 编辑
application.yml
,增加数据库连贯参数:
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://127.0.0.1:3307/oauth_server?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 1 druid: driver-class-name: com.mysql.cj.jdbc.Driver initial-size: 5 max-active: 50 max-wait: 60000 min-idle: 5
筹备工作
- 新建
UserDTO
类,实现org.springframework.security.core.userdetails.UserDetails
接口;
/** * @author lanweihong 986310747@qq.com */@Datapublic class UserDTO implements Serializable, UserDetails { private static final long serialVersionUID = 5538522337801286424L; private String userName; private String password; private Set<SimpleGrantedAuthority> authorities; public Collection<? extends GrantedAuthority> getAuthorities() { return this.authorities; } public String getPassword() { return this.password; } public String getUsername() { return this.userName; } public boolean isAccountNonExpired() { return true; } public boolean isAccountNonLocked() { return true; } public boolean isCredentialsNonExpired() { return true; } public boolean isEnabled() { return true; }}
- 新建类
UserDetailsServiceImpl
,实现org.springframework.security.core.userdetails.UserDetailsService
接口,用于校验用户凭据。
@Servicepublic class UserDetailsServiceImpl implements UserDetailsService { private PasswordEncoder passwordEncoder; @Autowired public void setPasswordEncoder(PasswordEncoder passwordEncoder) { this.passwordEncoder = passwordEncoder; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // TODO 理论开发中,这里请批改从数据库中查问... UserDTO user = new UserDTO(); user.setUserName(username); // 明码为 123456 ,且加密 user.setPassword(passwordEncoder.encode("123456")); return user; }}
以上用户配置用于测试,任意用户名,但明码为 123456,理论生产中务必批改为从数据库中读取校验。
配置认证受权服务器
新建类
Oauth2ServerConfig
,继承org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter
类;在Oauth2ServerConfig
类上 增加注解@EnableAuthorizationServer
。框架提供了几个默认的端点:
/oauth/authorize
:受权端点/oauth/token
:获取令牌端点/oauth/confirm_access
:用户确认受权端点/oauth/check_token
:校验令牌端点/oauth/error
:用于在受权服务器中出现谬误/oauth/token_key
:获取 jwt 公钥端点
继承
AuthorizationServerConfigurerAdapter
类后,咱们须要重写以下三个办法扩大实现咱们的需要。configure(ClientDetailsServiceConfigurer clients)
:用于定义、初始化客户端信息configure(AuthorizationServerEndpointsConfigurer endpoints)
:用于定义受权令牌端点及服务configure(AuthorizationServerSecurityConfigurer security)
:用于定义令牌端点的平安束缚
配置客户端详细信息
ClientDetailsServiceConfigurer
用于定义 内存 中或 基于JDBC存储实现 的客户端,其重要的几个属性有:
clientId
:客户端id,必填;clientSecret
:客户端密钥;authorizedGrantTypes
:客户端受权类型,有 5 种模式:authorization_code
、password
、client_credentials
、implicit
、refresh_token
;scope
:受权范畴;accessTokenValiditySeconds
:access_token
无效工夫,单位为秒,默认为 12 小时;refreshTokenValiditySeconds
:refresh_token
无效工夫,单位为秒,默认为 30 天;
客户端信息个别保留在 Redis 或 数据库中,本例中客户端信息保留在 MySQL 中;
基于JDBC存储 模式须要创立数据表,官网提供了建表的 SQL 语句,可拜访 schema.sql 获取 SQL ;
- 应用以下 SQL(实用于MySQL) 来建表:
CREATE TABLE `oauth_client_details` ( `client_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `access_token_validity` int(11) NULL DEFAULT NULL, `refresh_token_validity` int(11) NULL DEFAULT NULL, `additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, PRIMARY KEY (`client_id`) USING BTREE) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
- 增加一条客户端信息用于测试:
INSERT INTO `oauth_client_details` VALUES ('auth-server', NULL, '$2a$10$mcEwJ8qqhk2DYIle6VfhEOZHRdDbCSizAQbIwBR7tTuv9Q7Fca9Gi', 'all', 'password,refresh_token', '', NULL, NULL, NULL, NULL, NULL);
其中明码 123456 应用 BCryptPasswordEncoder
加密,加密后字符为 $2a$10$mcEwJ8qqhk2DYIle6VfhEOZHRdDbCSizAQbIwBR7tTuv9Q7Fca9Gi
。
- 配置
ClientDetailsServiceConfigurer
,指定客户端信息:
@Configuration@EnableAuthorizationServerpublic class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter { private final DataSource dataSource; private final PasswordEncoder passwordEncoder; @Autowired public Oauth2ServerConfig(DataSource dataSource, PasswordEncoder passwordEncoder) { this.dataSource = dataSource; this.passwordEncoder = passwordEncoder; } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { // 应用基于 JDBC 存储模式 JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource); // client_secret 加密 clientDetailsService.setPasswordEncoder(passwordEncoder); clients.withClientDetails(clientDetailsService); }}
配置受权令牌端点及服务
配置 AuthorizationServerEndpointsConfigurer
须要指定 AuthenticationManager
及 UserDetailService
,尤其是应用明码模式时,必须指定 AuthenticationManager
,否则会报 Unsupported grant type: password
谬误。
- 新建
WebSecurityConfig
类,继承org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
类,重写authenticationManagerBean()
办法,并定义须要用到的PasswordEncoder
;
@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true)public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // 反对跨域申请 .cors() .and() // 禁用 CSRF .csrf().disable() .formLogin().disable() .httpBasic().disable() .logout().disable() .authorizeRequests() .antMatchers("/oauth/token").permitAll(); .anyRequest().authenticated(); } /** * 重写 authenticationManagerBean() * @return * @throws Exception */ @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }}
- 配置
AuthorizationServerEndpointsConfigurer
:
@Configuration@EnableAuthorizationServerpublic class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter { private final UserDetailsServiceImpl userDetailsService; /** * 明码模式 grant_type:password 需指定 AuthenticationManager */ private final AuthenticationManager authenticationManager; @Autowired public Oauth2ServerConfig(UserDetailsServiceImpl userDetailsService, AuthenticationManager authenticationManager) { this.userDetailsService = userDetailsService; this.authenticationManager = authenticationManager; } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints // 开启明码模式受权 .authenticationManager(authenticationManager) .userDetailsService(userDetailsService); }}
应用 JWT 作为令牌格局
生成 JWT 密钥对
应用 JDK 的 keytool 工具生成 JKS 密钥对 jwt.jks
,并将 jwt.jks
放到 resources
目录下。
定位至 JDK 目录下的 bin
目录,执行以下命令生成密钥对,记住口令密钥,代码中须要用到密钥来读取密钥对,以下命令以 123456
为例:
keytool -genkey -alias weihong -keyalg RSA -keypass 123456 -keystore jwt.jks -storepass 123456
参数阐明:
-genkey 生成密钥-alias 别名-keyalg 密钥算法-keypass 密钥口令-keystore 生成密钥对的存储门路和名称-storepass 密钥对口令
定义 token 转换器
在 Oauth2ServerConfig
类中定义 accessTokenConverter()
及 keyPair()
:
/** * token 转换器 * 默认是 uuid 格局,咱们在这里指定 token 格局为 jwt * 应用非对称加密算法对 token 签名 * @return */ @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); // 应用非对称加密算法对 token 签名 converter.setKeyPair(keyPair()); return converter; } @Bean public KeyPair keyPair() { // 从 classpath 目录下的证书 jwt.jks 中获取秘钥对,输出在以上生成密钥对设置的明码: 123456,这里应用硬编码,倡议写到配置文件中 KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray()); return keyStoreKeyFactory.getKeyPair("weihong", "123456".toCharArray()); }
指定令牌存储策略为 JWT
配置 AuthorizationServerEndpointsConfigurer
的令牌存储策略为 JWT,指定 accessTokenConverter
为咱们定义好的 accessTokenConverter()
:
@Configuration@EnableAuthorizationServerpublic class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter { private final UserDetailsServiceImpl userDetailsService; /** * 明码模式 grant_type:password 需指定 AuthenticationManager */ private final AuthenticationManager authenticationManager; @Autowired public Oauth2ServerConfig(UserDetailsServiceImpl userDetailsService, AuthenticationManager authenticationManager) { this.userDetailsService = userDetailsService; this.authenticationManager = authenticationManager; } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { endpoints // 开启明码模式受权 .authenticationManager(authenticationManager) .userDetailsService(userDetailsService) // 指定令牌存储策略 .accessTokenConverter(accessTokenConverter()); } /** * token 转换器 * 默认是 uuid 格局,咱们在这里指定 token 格局为 jwt * @return */ @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); // 应用非对称加密算法对 token 签名 converter.setKeyPair(keyPair()); return converter; } @Bean public KeyPair keyPair() { // 从 classpath 目录下的证书 jwt.jks 中获取秘钥对 KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray()); return keyStoreKeyFactory.getKeyPair("weihong", "123456".toCharArray()); }}
扩大 JWT 存储内容
有时候咱们须要扩大 JWT 存储的内容,比方存储一些用户数据、权限信息等。咱们能够定义 TokenEnhancer
或继承 TokenEnhancer
来实现 JWT 内容增强器:
@Bean public TokenEnhancer tokenEnhancer() { return (oAuth2AccessToken, oAuth2Authentication) -> { Map<String, Object> map = new HashMap<>(1); UserDTO userDTO = (UserDTO) oAuth2Authentication.getPrincipal(); map.put("userName", userDTO.getUsername()); // TODO 其余信息能够自行添加 ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(map); return oAuth2AccessToken; }; }
配置 AuthorizationServerEndpointsConfigurer
JWT 内容增强器:
@Configuration@EnableAuthorizationServerpublic class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter { private final UserDetailsServiceImpl userDetailsService; private final AuthenticationManager authenticationManager; @Autowired public Oauth2ServerConfig(UserDetailsServiceImpl userDetailsService, AuthenticationManager authenticationManager) { this.userDetailsService = userDetailsService; this.authenticationManager = authenticationManager; } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { TokenEnhancerChain enhancerChain = new TokenEnhancerChain(); List<TokenEnhancer> delegates = new ArrayList<>(); delegates.add(tokenEnhancer()); delegates.add(accessTokenConverter()); // 配置 JWT 内容加强 enhancerChain.setTokenEnhancers(delegates); endpoints // 开启明码模式受权 .authenticationManager(authenticationManager) .userDetailsService(userDetailsService) .accessTokenConverter(accessTokenConverter()) .tokenEnhancer(enhancerChain); } /** * token 转换器 * 默认是 uuid 格局,咱们在这里指定 token 格局为 jwt * @return */ @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); // 应用非对称加密算法对 token 签名 converter.setKeyPair(keyPair()); return converter; } @Bean public KeyPair keyPair() { KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray()); return keyStoreKeyFactory.getKeyPair("weihong", "123456".toCharArray()); } /** * JWT 内容增强器,用于扩大 JWT 内容,能够保留用户数据 * @return */ @Bean public TokenEnhancer tokenEnhancer() { return (oAuth2AccessToken, oAuth2Authentication) -> { Map<String, Object> map = new HashMap<>(1); UserDTO userDTO = (UserDTO) oAuth2Authentication.getPrincipal(); map.put("userName", userDTO.getUsername()); // TODO 其余信息能够自行添加 ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(map); return oAuth2AccessToken; }; }}
应用 Redis 存储 token
- 在
pom.xml
中增加依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency>
- 编辑
application.yml
,增加 Redis 连贯参数:
spring: redis: host: localhost port: 6379 password: 1
- 增加 token 保留至 redis 的配置:
@Configurationpublic class RedisTokenStoreConfig { @Resource private RedisConnectionFactory connectionFactory; @Bean public TokenStore redisTokenStore() { return new RedisTokenStore(connectionFactory); }}
- 在认证服务配置中指定 token 存储形式:
@Configuration@EnableAuthorizationServerpublic class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter { private final UserDetailsServiceImpl userDetailsService; /** * 明码模式 grant_type:password 需指定 AuthenticationManager */ private final AuthenticationManager authenticationManager; private final TokenStore tokenStore; @Autowired public Oauth2ServerConfig(UserDetailsServiceImpl userDetailsService, AuthenticationManager authenticationManager, @Qualifier("redisTokenStore") TokenStore tokenStore) { this.userDetailsService = userDetailsService; this.authenticationManager = authenticationManager; this.tokenStore = tokenStore; } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints // 开启明码模式受权 .authenticationManager(authenticationManager) .userDetailsService(userDetailsService) // 设置 token 存储形式 .tokenStore(tokenStore); }}
配置受权令牌平安束缚
@Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security // 容许表单认证 .allowFormAuthenticationForClients() // 凋谢 /oauth/token_key 获取 token 加密公钥 .tokenKeyAccess("permitAll()") // 凋谢 /oauth/check_token .checkTokenAccess("permitAll()"); }
认证受权服务配置残缺代码
Oauth2ServerConfig
:
@Configuration@EnableAuthorizationServerpublic class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter { /** * 数据源 */ private final DataSource dataSource; private final UserDetailsServiceImpl userDetailsService; /** * 明码模式 grant_type:password 需指定 AuthenticationManager */ private final AuthenticationManager authenticationManager; private final PasswordEncoder passwordEncoder; private final TokenStore tokenStore; @Autowired public Oauth2ServerConfig(DataSource dataSource, UserDetailsServiceImpl userDetailsService, AuthenticationManager authenticationManager, PasswordEncoder passwordEncoder, @Qualifier("redisTokenStore") TokenStore tokenStore) { this.dataSource = dataSource; this.userDetailsService = userDetailsService; this.authenticationManager = authenticationManager; this.passwordEncoder = passwordEncoder; this.tokenStore = tokenStore; } @Override public void configure(AuthorizationServerSecurityConfigurer security) { security // 容许表单认证 .allowFormAuthenticationForClients() // 需通过认证后能力拜访 /oauth/token_key 获取 token 加密公钥 .tokenKeyAccess("permitAll()") // 凋谢 /oauth/check_token .checkTokenAccess("permitAll()"); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { // 应用基于 JDBC 存储模式 JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource); clientDetailsService.setPasswordEncoder(passwordEncoder); clients.withClientDetails(clientDetailsService); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { TokenEnhancerChain enhancerChain = new TokenEnhancerChain(); List<TokenEnhancer> delegates = new ArrayList<>(); delegates.add(tokenEnhancer()); delegates.add(accessTokenConverter()); // 配置 JWT 内容加强 enhancerChain.setTokenEnhancers(delegates); endpoints // 开启明码模式受权 .authenticationManager(authenticationManager) .userDetailsService(userDetailsService) .accessTokenConverter(accessTokenConverter()) .tokenEnhancer(enhancerChain) // 设置 token 存储形式 .tokenStore(tokenStore); } /** * token 转换器 * 默认是 uuid 格局,咱们在这里指定 token 格局为 jwt * @return */ @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); // 应用非对称加密算法对 token 签名 converter.setKeyPair(keyPair()); return converter; } @Bean public KeyPair keyPair() { KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray()); return keyStoreKeyFactory.getKeyPair("weihong", "123456".toCharArray()); } /** * JWT 内容增强器,用于扩大 JWT 内容,能够保留用户数据 * @return */ @Bean public TokenEnhancer tokenEnhancer() { return (oAuth2AccessToken, oAuth2Authentication) -> { Map<String, Object> map = new HashMap<>(1); UserDTO userDTO = (UserDTO) oAuth2Authentication.getPrincipal(); map.put("userName", userDTO.getUsername()); // TODO 其余信息能够自行添加 ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(map); return oAuth2AccessToken; }; }}
WebSecurityConfig
:
@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true)public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // 反对跨域申请 .cors() .and() // 禁用 CSRF .csrf().disable() .formLogin().disable() .httpBasic().disable() .logout().disable() .authorizeRequests() .antMatchers("/oauth/token").permitAll() .anyRequest().authenticated(); } /** * 重写 authenticationManagerBean() * @return * @throws Exception */ @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }}
测试获取 token
获取 token
运行我的项目,应用 Postman 拜访 /oauth/token
端点,并传参数,参数必须与咱们配置的内容统一;
胜利获取到 token ,格局如下:
{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MTA1NjkzNDksInVzZXJOYW1lIjoiYWRtaW4iLCJ1c2VyX25hbWUiOiJhZG1pbiIsImp0aSI6IjgzM2VjZDdkLThmMzctNDAxOS04YWQwLTBlODI3ZTM4M2U5YyIsImNsaWVudF9pZCI6Im9hdXRoLXNlcnZlciIsInNjb3BlIjpbImFsbCJdfQ.ke6fWfGMOXhppF-6XXftZJx0w8hSnTKYYwvi_As66Ats9_AFqrHCZiuHA_M5LD2bJzahFC__-IUr_6g6ajx-IlLpSPqs3izgbuOPcTzCivfznGn38W5kYPe1ygQ8mJzN97yAT1QKZGMAT0nr7HR5NSG2MHYPbHuWSHp4KVIf7XQbszmXVPKEeQsv64QZ8O1xe9XtshF4mtZsxfLEGxAZEPSkoyJi-vwH6qKnvVh8EI8zgwTX5cIh6Gj4rcEfDiJYNAiI_NanuNA1wBoI1eD-QYSUQ5XXW1Q4vQAnjQMQwvTZYn1hGdAbeHQrA9hPLw5_Axeq8_meWpNobla_rRYkLQ", "token_type": "bearer", "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiI4MzNlY2Q3ZC04ZjM3LTQwMTktOGFkMC0wZTgyN2UzODNlOWMiLCJleHAiOjE2MTMxMTgxNDksInVzZXJOYW1lIjoiYWRtaW4iLCJqdGkiOiIwMDljZjhmNy05OTE5LTQyODEtYjUxNS02NjM3ZjIyM2MyN2YiLCJjbGllbnRfaWQiOiJvYXV0aC1zZXJ2ZXIifQ.bFMQRXCOz2rvu8QhTOjjlM66Fe3EM5F2wUXI-3dQOxnu2AOCsCJKUZdT0AhsnJkSI5Ewc1jUd7TiUifj9p6CYzIuHtnPORUUE67vt7eiKjpdNdNaUIvXzSoAcx-B5FgYynKslZm5S6WwqQMEb6jFMeg1iN3DphDPbjUMCP2qZevm6fNTT0b7PzxE0POepqqEnyjIS1YOnMnyHkgSAQCtYMAwWATalS4tMFNRb-hbE2MGi-U1j3Z1Mq79x9Uce8ZXjD2a_sCE9x0fqTixO-pRUrQNrIqiX_bZlw96xktnUQy2wCoJiZRxKjZyRhPLxOQPR7FUyd8yFXjCHR_yf5mwYw", "expires_in": 43199, "scope": "all", "userName": "admin", "jti": "833ecd7d-8f37-4019-8ad0-0e827e383e9c"}
将返回的 token 复制到 https://jwt.io/ 解析,发现已正确解析。
校验 token
- 应用 Postman 拜访
/oauth/check_token
端点,咱们试着增加谬误的token
,而后发送申请校验,发现返回谬误;
- 应用 Postman 拜访
/oauth/check_token
端点,咱们应用正确的token
校验,胜利返回信息;
刷新 token
应用 Postman 拜访 /oauth/token
端点,其中参数 grant_type
应用 refresh_token
,refresh_token
内容为咱们从 /oauth/token
获取的 refresh_token
(留神不是 access_token
);其余参数请自行配置,可参考如下:
搭建资源服务
- 新建
module
,在pom.xml
中增加依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId></dependency>
- 在
application.yml
中增加 token 校验的相干参数,将 token 校验地址改为认证服务的 token 校验地址:
oauth2: resource-id: resource-server # token 校验地址 check-token-url: http://127.0.0.1:8089/oauth/check_token client-id: oauth-server client-secret: 123456
配置资源服务
- 新建
ResourceServerConfig
类,继承org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter
,增加注解@EnableResourceServer
,并配置token
校验服务;
@Configuration@EnableResourceServerpublic class ResourceServerConfig extends ResourceServerConfigurerAdapter { @Value("${oauth2.check-token-url}") private String checkTokenUrl; @Value("${oauth2.resource-id}") private String resourceId; @Value("${oauth2.client-id}") private String clientId; @Value("${oauth2.client-secret}") private String clientSecret; @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources.resourceId(resourceId).stateless(true); resources.tokenServices(resourceServerTokenServices()); } @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll() .anyRequest() .authenticated() .and() .requestMatchers() .antMatchers("/users/**"); } /** * 配置 token 校验服务 * @return */ @Bean ResourceServerTokenServices resourceServerTokenServices() { RemoteTokenServices remoteTokenServices = new RemoteTokenServices(); remoteTokenServices.setCheckTokenEndpointUrl(checkTokenUrl); remoteTokenServices.setClientId(clientId); remoteTokenServices.setClientSecret(clientSecret); remoteTokenServices.setAccessTokenConverter(accessTokenConverter()); return remoteTokenServices; } @Bean public AccessTokenConverter accessTokenConverter() { return new DefaultAccessTokenConverter(); }}
- 增加 Controller
@RestControllerpublic class HomeController { @GetMapping("/users") public Map<String, Object> test(Authentication authentication) { Map<String, Object> data = new HashMap<>(1); data.put("user", authentication.getPrincipal()); return data; }}
测试
应用 Postman 拜访 /users
,返回未受权谬误:
应用 Postman 拜访 /users
,带上 token
拜访,胜利申请并获取到用户数据;
我的项目源码地址
https://github.com/lanweihong/spring-securuty-oauth2-jwt
参考文档
- 了解OAuth 2.0
- OAuth 2 Developers Guide
- Spring Security OAuth2自定义令牌配置