本篇文章讲解如何通过Springboot2集成验证服务Token,以及资源服务的用法(更多官方关于 Oauth2)
主要使用Spring Boot2和SpringSecurity5
在oauth2协议中,一个应用会有自己的clientId和clientSecret(从认证方申请),由认证方下发clientId和secret
授权服务 Authorization Server
构建Authorization Server,这里我使用了SpringSecurity5 和 SpringBoot 2.0.6版本
1. 项目目录结构
2. pom.xml依赖组件
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-web
net.sf.json-lib
json-lib-ext-spring
org.springframework.boot
spring-boot-starter-security
org.springframework.security.oauth.boot
spring-security-oauth2-autoconfigure
org.springframework.boot
spring-boot-starter-jdbc
org.springframework.boot
spring-boot-configuration-processor
true
com.h2database
h2
runtime
3. 这里为演示,使用了H2数据库
以下是Spring Security需要用到的OAuth2 SQL
CREATE TABLE IF NOT EXISTS oauth_client_details (
client_id VARCHAR(256) PRIMARY KEY,
resource_ids VARCHAR(256),
client_secret VARCHAR(256) NOT NULL,
scope VARCHAR(256),
authorized_grant_types VARCHAR(256),
web_server_redirect_uri VARCHAR(256),
authorities VARCHAR(256),
access_token_validity INTEGER,
refresh_token_validity INTEGER,
additional_information VARCHAR(4000),
autoapprove VARCHAR(256)
);
CREATE TABLE IF NOT EXISTS oauth_client_token (
token_id VARCHAR(256),
token BLOB,
authentication_id VARCHAR(256) PRIMARY KEY,
user_name VARCHAR(256),
client_id VARCHAR(256)
);
CREATE TABLE IF NOT EXISTS oauth_access_token (
token_id VARCHAR(256),
token BLOB,
authentication_id VARCHAR(256),
user_name VARCHAR(256),
client_id VARCHAR(256),
authentication BLOB,
refresh_token VARCHAR(256)
);
CREATE TABLE IF NOT EXISTS oauth_refresh_token (
token_id VARCHAR(256),
token BLOB,
authentication BLOB
);
CREATE TABLE IF NOT EXISTS oauth_code (
code VARCHAR(256), authentication BLOB
);
使用下面这句:
INSERT INTO oauth_client_details (client_id, client_secret, scope, authorized_grant_types, authorities, access_token_validity)
VALUES ('clientId', '{bcrypt}$2a$10$vCXMWCn7fDZWOcLnIEhmK.74dvK1Eh8ae2WrWlhr2ETPLoxQctN4.', 'read,write', 'password,refresh_token,client_credentials', 'ROLE_CLIENT', 300);
特别要注意:这里是下发clientId和secret,值分别为clientId和secret(这两个值后面要用到)
这里的client_secret列值,由
bcrypt生成
而{bcrypt}前缀写法,这是
Spring Security 5 DelegatingPasswordEncoder的新用法
access_token_validity列:表示token有效时间,单位秒,即300秒有效
同时,以下是User和Authority表(被org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl使用到)
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(256) NOT NULL,
password VARCHAR(256) NOT NULL,
enabled TINYINT(1),
UNIQUE KEY unique_username(username)
);
CREATE TABLE IF NOT EXISTS authorities (
username VARCHAR(256) NOT NULL,
authority VARCHAR(256) NOT NULL,
PRIMARY KEY(username, authority)
);
同样,使用下面的sql,初始化user和authority
INSERT INTO users (id, username, password, enabled) VALUES (1, 'user', '{bcrypt}$2a$10$cyf5NfobcruKQ8XGjUJkEegr9ZWFqaea6vjpXWEaSqTa2xL9wjgQC', 1);
INSERT INTO authorities (username, authority) VALUES ('user', 'ROLE_USER');
注意:用户名是user,password的值是:pass,后面会用到
Spring Security Configuration 部分
4. WebSecurityConfiguration.java文件
package com.md.demo.oauth.opaque.config;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.sql.DataSource;
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final DataSource dataSource;
private PasswordEncoder passwordEncoder;
private UserDetailsService userDetailsService;
public WebSecurityConfiguration(final DataSource dataSource) {
this.dataSource = dataSource;
}
// 排除路径验证
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/hello");
}
@Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
if (passwordEncoder == null) {
passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
return passwordEncoder;
}
@Bean
@Override
public UserDetailsService userDetailsService() {
if (userDetailsService == null) {
userDetailsService = new JdbcDaoImpl();
((JdbcDaoImpl) userDetailsService).setDataSource(dataSource);
}
return userDetailsService;
}
}
一起使用@EnableWebSecurity注解和WebSecurityConfigurerAdapter,可以提供web基础安全机制
5. 附加说明
如果你使用了springboot DataSource数据源对象,它会自动装配,你不需要再自定义,只需要注入即可。它需要注入到由Spring Security提供的UserDetailsService中,结合使用JdbcDaoImpl。如果需要,你也能替换它去自定义实现。
6. 验证服务配置
验证服务验证客户端和用户凭证,同时生成token
package com.md.demo.oauth.opaque.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
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.configuration.EnableAuthorizationServer;
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.ClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import javax.sql.DataSource;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
private final DataSource dataSource;
private final PasswordEncoder passwordEncoder;
private final AuthenticationManager authenticationManager;
private TokenStore tokenStore;
public AuthorizationServerConfiguration(final DataSource dataSource, final PasswordEncoder passwordEncoder,
final AuthenticationManager authenticationManager) {
this.dataSource = dataSource;
this.passwordEncoder = passwordEncoder;
this.authenticationManager = authenticationManager;
}
@Bean
public TokenStore tokenStore() {
if (tokenStore == null) {
tokenStore = new JdbcTokenStore(dataSource);
}
return tokenStore;
}
@Bean
public DefaultTokenServices tokenServices(final ClientDetailsService clientDetailsService) {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(tokenStore());
tokenServices.setClientDetailsService(clientDetailsService);
tokenServices.setAuthenticationManager(authenticationManager);
return tokenServices;
}
@Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.authenticationManager(authenticationManager)
.tokenStore(tokenStore());
}
@Override
public void configure(final AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer.passwordEncoder(passwordEncoder)
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
}
7. 测试接口访问
package com.md.demo.oauth.rest;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.Principal;
@RestController
@RequestMapping("/profile")
public class ProfileController {
//http://localhost:9001/profile/me?access_token=xxxxxx
@GetMapping("/me")
public ResponseEntity get(final Principal principal) {
return ResponseEntity.ok(principal);
}
/*
* 通过以下接口Post请求获得access_token:
* http://localhost:9001/oauth/token?grant_type=password&username=user&password=pass
*
* 注意:Authorization中使用BasicAuth,Username和Password分别为:clientId,secret
*/
}
资源服务 Resource Server
1. 项目目录结构
2. pom.xml依赖组件
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-web
net.sf.json-lib
json-lib-ext-spring
org.springframework.boot
spring-boot-starter-security
org.springframework.security.oauth.boot
spring-security-oauth2-autoconfigure
org.springframework.boot
spring-boot-configuration-processor
true
commons-io
commons-io
3. 接口访问(受保护)
package com.md.demo.oauth.rest;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.Principal;
@RestController
@RequestMapping("/me")
public class UserController {
//http://localhost:9101/me?access_token=xxxxxx
@GetMapping
@PreAuthorize("hasRole('ROLE_USER')")
public ResponseEntity get(final Principal principal) {
return ResponseEntity.ok(principal);
}
/*
* 通过以下接口Post请求获得access_token:
* http://localhost:9001/oauth/token?grant_type=password&username=user&password=pass
*
* 注意:Authorization中使用BasicAuth,Username和Password分别为:clientId,secret
*/
}
@PreAuthorize注解验证用户的权限去是否执行代码,同时需要启用prePost注解
4. Web安全配置
package com.md.demo.oauth.opaque.ds.config;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration {
}
prePostEnabled默认是false,使@PreAuthorize生效,则设为true
5. 资源服务配置
package com.md.demo.oauth.opaque.ds.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
private static final String ROOT_PATTERN = "/**";
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 排除路径验证
.antMatchers("/hello")
.permitAll()
.antMatchers(ROOT_PATTERN)
.authenticated();
}
}
6. application.yml配置
server:
port: 9101
# 设置url,把token转换成authentication对象
security:
oauth2:
resource:
user-info-uri: http://localhost:9001/profile/me
spring:
jackson:
serialization:
INDENT_OUTPUT: true
1. 访问受保护接口:http://localhost:9101/me,默认失败
2. 获得token接口:http://localhost:9001/oauth/token?grant_type=password&username=user&password=pass
3. 再访问受保护接口:http://localhost:9101/me?access_token=8df31c53-e616-47d1-802e-9b00adf64266,即可访问成功
我的Github源码仓库地址:
SpringBoot从入门到精通教程(二十三)- Oauth2+JWT集成/SpringSecurity
SpringBoot从入门到精通教程
至此,全部介绍就结束了
------------------------------------------------------
------------------------------------------------------
关于我(个人域名)
我的开源项目集Github
期望和大家一起学习,一起成长,共勉,O(∩_∩)O谢谢
欢迎交流问题,可加个人QQ 469580884,
或者,加我的群号 751925591,一起探讨交流问题
不讲虚的,只做实干家
Talk is cheap,show me the code