Introduction to the
OAuth is an open network standard about authorization, widely used in the world, the current version is version 2.0. This article focuses on the implementation of OAuth2 by Spring Boot project, if you are not very familiar with OAuth2, you can first understand OAuth 2.0 – Ruan Yifeng, this is a good popular science article for OAuth2.
OAuth2 overview
Oauth2 is divided into four modes according to different usage scenarios
- Authorization Code
- Simplified patterns (Implicit)
- Password mode (Resource owner Password Credentials)
- Client credentials
In the project, we usually use the authorization code mode, which is the most complex mode among the four modes. Usually, microblog and QQ third-party login frequently appear in websites will adopt this mode.
Oauth2 authorization consists of two main parts:
- Authorization Server: indicates authentication services
- Resource Server: Resource service
In a real project, these two services can be deployed on the same server or deployed separately. Here’s how to use it in conjunction with Spring Boot.
Quick learning
Spring Security has been covered in previous articles; this section does not go into the details of the configuration involved in Spring Security. If you are not familiar with Spring Security, go to Spring Boot Security for details.
Build table
Client information can be stored in memory, redis, and databases. Redis and database storage are commonly used in real projects. This article uses a database. Spring 0Auth2 has designed the database tables and is immutable. For table and field description, see Oauth2 database table description.
The script for creating the 0Auth2 database is as follows:
DROP TABLE IF EXISTS `clientdetails`;
DROP TABLE IF EXISTS `oauth_access_token`;
DROP TABLE IF EXISTS `oauth_approvals`;
DROP TABLE IF EXISTS `oauth_client_details`;
DROP TABLE IF EXISTS `oauth_client_token`;
DROP TABLE IF EXISTS `oauth_refresh_token`;
CREATE TABLE `clientdetails` (
`appId` varchar(128) NOT NULL.`resourceIds` varchar(256) DEFAULT NULL.`appSecret` varchar(256) DEFAULT NULL.`scope` varchar(256) DEFAULT NULL.`grantTypes` varchar(256) DEFAULT NULL.`redirectUrl` varchar(256) DEFAULT NULL.`authorities` varchar(256) DEFAULT NULL.`access_token_validity` int(11) DEFAULT NULL.`refresh_token_validity` int(11) DEFAULT NULL.`additionalInformation` varchar(4096) DEFAULT NULL.`autoApproveScopes` varchar(256) DEFAULT NULL,
PRIMARY KEY (`appId`))ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `oauth_access_token` (
`token_id` varchar(256) DEFAULT NULL.`token` blob.`authentication_id` varchar(128) NOT NULL.`user_name` varchar(256) DEFAULT NULL.`client_id` varchar(256) DEFAULT NULL.`authentication` blob.`refresh_token` varchar(256) DEFAULT NULL,
PRIMARY KEY (`authentication_id`))ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `oauth_approvals` (
`userId` varchar(256) DEFAULT NULL.`clientId` varchar(256) DEFAULT NULL.`scope` varchar(256) DEFAULT NULL.`status` varchar(10) DEFAULT NULL.`expiresAt` datetime DEFAULT NULL.`lastModifiedAt` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `oauth_client_details` (
`client_id` varchar(128) 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;
CREATE TABLE `oauth_client_token` (
`token_id` varchar(256) DEFAULT NULL.`token` blob.`authentication_id` varchar(128) NOT NULL.`user_name` varchar(256) DEFAULT NULL.`client_id` varchar(256) DEFAULT NULL,
PRIMARY KEY (`authentication_id`))ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `oauth_code`;
CREATE TABLE `oauth_code` (
`code` varchar(256) DEFAULT NULL.`authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `oauth_refresh_token` (
`token_id` varchar(256) DEFAULT NULL.`token` blob.`authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Copy the code
For testing purposes, let’s insert a piece of client information.
INSERT INTO `oauth_client_details` VALUES ('dev'.' '.'dev'.'app'.'password,client_credentials,authorization_code,refresh_token'.'http://www.baidu.com'.' '.3600.3600.'{\"country\":\"CN\",\"country_code\":\"086\"}'.'false');
Copy the code
The table for users, permissions, and roles is as follows:
DROP TABLE IF EXISTS `user`;
DROP TABLE IF EXISTS `role`;
DROP TABLE IF EXISTS `user_role`;
DROP TABLE IF EXISTS `role_permission`;
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `user` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL.`password` varchar(255) NOT NULL,
PRIMARY KEY (`id`));CREATE TABLE `role` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`));CREATE TABLE `user_role` (
`user_id` bigint(11) NOT NULL.`role_id` bigint(11) NOT NULL
);
CREATE TABLE `role_permission` (
`role_id` bigint(11) NOT NULL.`permission_id` bigint(11) NOT NULL
);
CREATE TABLE `permission` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`url` varchar(255) NOT NULL.`name` varchar(255) NOT NULL.`description` varchar(255) NULL.`pid` bigint(11) NOT NULL,
PRIMARY KEY (`id`));INSERT INTO user (id, username, password) VALUES (1.'user'.'e10adc3949ba59abbe56e057f20f883e');
INSERT INTO user (id, username , password) VALUES (2.'admin'.'e10adc3949ba59abbe56e057f20f883e');
INSERT INTO role (id.name) VALUES (1.'USER');
INSERT INTO role (id.name) VALUES (2.'ADMIN');
INSERT INTO permission (id.url.name, pid) VALUES (1.'/ * *'.' '.0);
INSERT INTO permission (id.url.name, pid) VALUES (2.'/ * *'.' '.0);
INSERT INTO user_role (user_id, role_id) VALUES (1.1);
INSERT INTO user_role (user_id, role_id) VALUES (2.2);
INSERT INTO role_permission (role_id, permission_id) VALUES (1.1);
INSERT INTO role_permission (role_id, permission_id) VALUES (2.2);
Copy the code
The project structure
resources
|____templates
| |____login.html
| |____application.yml
java
|____com
| |____gf
| | |____SpringbootSecurityApplication.java
| | |____config
| | | |____SecurityConfig.java
| | | |____MyFilterSecurityInterceptor.java
| | | |____MyInvocationSecurityMetadataSourceService.java
| | | |____ResourceServerConfig.java
| | | |____WebResponseExceptionTranslateConfig.java
| | | |____AuthorizationServerConfiguration.java
| | | |____MyAccessDecisionManager.java
| | |____entity
| | | |____User.java
| | | |____RolePermisson.java
| | | |____Role.java
| | |____mapper
| | | |____PermissionMapper.java
| | | |____UserMapper.java
| | | |____RoleMapper.java
| | |____controller
| | | |____HelloController.java
| | | |____MainController.java
| | |____service
| | | |____MyUserDetailsService.java
Copy the code
The key code
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.3. RELEASE</version>
</dependency>
Copy the code
SecurityConfig
To support password mode, you need to configure AuthenticationManager
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailsService userService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// Verify the user
auth.userDetailsService( userService ).passwordEncoder( new PasswordEncoder() {
// Encrypt the password
@Override
public String encode(CharSequence charSequence) {
System.out.println(charSequence.toString());
return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
}
// Match the password
@Override
public boolean matches(CharSequence charSequence, String s) {
String encode = DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
boolean res = s.equals( encode );
returnres; }}); }@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.requestMatchers()
.antMatchers("/oauth/**"."/login"."/login-error")
.and()
.authorizeRequests()
.antMatchers("/oauth/**").authenticated()
.and()
.formLogin().loginPage( "/login" ).failureUrl( "/login-error" );
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean(a) throws Exception{
return super.authenticationManager();
}
@Bean
public PasswordEncoder passwordEncoder(a) {
return new PasswordEncoder() {
@Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
@Override
public boolean matches(CharSequence charSequence, String s) {
returnObjects.equals(charSequence.toString(),s); }}; }}Copy the code
AuthorizationServerConfiguration authentication server configuration
/** * Authentication server configuration */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
/** * Insert permission validation controller to support password grant type */
@Autowired
private AuthenticationManager authenticationManager;
/** * Inject userDetailsService, and enable refresh_token with */
@Autowired
private MyUserDetailsService userDetailsService;
/** * data source */
@Autowired
private DataSource dataSource;
/** * Set the way to save the token. There are five ways
@Autowired
private TokenStore tokenStore;
@Autowired
private WebResponseExceptionTranslator webResponseExceptionTranslator;
@Bean
public TokenStore tokenStore(a) {
return new JdbcTokenStore( dataSource );
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()")
.checkTokenAccess("permitAll()")
.allowFormAuthenticationForClients();
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// Enable the password authorization type
endpoints.authenticationManager(authenticationManager);
// Configure the token storage mode
endpoints.tokenStore(tokenStore);
// Information returned when a user fails to customize login or authentication
endpoints.exceptionTranslator(webResponseExceptionTranslator);
// To use refresh_token, you need to configure userDetailsServiceendpoints.userDetailsService( userDetailsService ); }}Copy the code
ResourceServerConfig Resource server configuration
/** * Resource provider configuration */
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
/ * * * set here need a token validation url * can be tossed off WebSecurityConfigurerAdapter, * for the same url, If both configuration validation * enter ResourceServerConfigurerAdapter, will be the priority for token authentication. Not for * WebSecurityConfigurerAdapter basic auth or forms authentication. * /
@Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatchers().antMatchers("/hi")
.and()
.authorizeRequests()
.antMatchers("/hi").authenticated(); }}Copy the code
MyFilterSecurityInterceptor filter
@Component
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
@Autowired
private FilterInvocationSecurityMetadataSource securityMetadataSource;
@Autowired
public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
super.setAccessDecisionManager(myAccessDecisionManager);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
HttpServletResponse response = (HttpServletResponse)servletResponse;
HttpServletRequest request = (HttpServletRequest)servletRequest;
response.setHeader("Access-Control-Allow-Origin"."*");
response.setHeader("Access-Control-Allow-Methods"."POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Allow-Headers".":x-requested-with,content-type");
filterChain.doFilter(servletRequest,servletResponse);
if(! request.getRequestURI().equals("/oauth/token")) { invoke(fi); }}public void invoke(FilterInvocation fi) throws IOException, ServletException {
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
// Execute the next interceptor
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null); }}@Override
publicClass<? > getSecureObjectClass() {return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource(a) {
return this.securityMetadataSource; }}Copy the code
This is the key code, and the rest of the class code is provided in the source code address below.
validation
Password authorization Mode
[Password mode requires parameters: username, password, grant_type, client_id, client_secret]
The request token
curl -X POST -d "username=admin&password=123456&grant_type=password&client_id=dev&client_secret=dev" http://localhost:8080/oauth/token
Copy the code
return
{
"access_token": "d94ec0aa-47ee-4578-b4a0-8cf47f0e8639"."token_type": "bearer"."refresh_token": "23503bc7-4494-4795-a047-98db75053374"."expires_in": 3475."scope": "app"
}
Copy the code
Access resources without carrying a token.
curl http://localhost:8080/hi\? name\=zhangsanCopy the code
An unauthorized message is displayed
{
"error": "unauthorized"."error_description": "Full authentication is required to access this resource"
}
Copy the code
Use a token to access resources
curl http://localhost:8080/hi\? name\=zhangsan\&access_token\=164471f7-6fc6-4890-b5d2-eb43bda3328aCopy the code
Return to the right
hi , zhangsan
Copy the code
The refresh token
curl -X POST -d 'grant_type=refresh_token&refresh_token=23503bc7-4494-4795-a047-98db75053374&client_id=dev&client_secret=dev' http://localhost:8080/oauth/token
Copy the code
return
{
"access_token": "ef53eb01-eb9b-46d8-bd58-7a0f9f44e30b"."token_type": "bearer"."refresh_token": "23503bc7-4494-4795-a047-98db75053374"."expires_in": 3599,
"scope": "app"
}
Copy the code
Client authorization mode
[Client mode requires parameters: grant_type, client_id, client_secret]
The request token
curl -X POST -d "grant_type=client_credentials&client_id=dev&client_secret=dev" http://localhost:8080/oauth/token
Copy the code
return
{
"access_token": "a7be47b3-9dc8-473e-967a-c7267682dc66"."token_type": "bearer"."expires_in": 3564."scope": "app"
}
Copy the code
Authorization code mode
Access code
Access the following address in the browser:
http://localhost:8080/oauth/authorize?response_type=code&client_id=dev&redirect_uri=http://www.baidu.com
Copy the code
Go to the login page and enter your account and password for authentication:
After authentication, the authorization confirmation page is redirected (if the autoapprove field in the oauth_client_details table is set to true, the authorization confirmation page is not displayed) :
After confirmation, it will jump to Baidu, and the address bar will bring the code parameter we want:
Change a token by code
curl -X POST -d "grant_type=authorization_code&code=qS03iu&client_id=dev&client_secret=dev&redirect_uri=http://www.baidu.com" http://localhost:8080/oauth/token
Copy the code
return
{
"access_token": "90a246fa-a9ee-4117-8401-ca9c869c5be9"."token_type": "bearer"."refresh_token": "23503bc7-4494-4795-a047-98db75053374"."expires_in": 3319."scope": "app"
}
Copy the code
reference
Segmentfault.com/a/119000001…
Stackoverflow.com/questions/2…
The source code
Github.com/gf-huanchup…