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…