8, Integrate the REST APIs document generation tool Swagger2
OAuth
An open standard that allows a user to allow a third-party application to access the user's private resources (such as photos, videos, contact lists) stored on a website without providing a user name and password to the third-party application. OAuth allows users to provide a token instead of a user name and password to access their data stored in a specific service provider. Each token authorizes a specific website to access specific resources within a specific period of time. In this way, OAuth allows users to authorize third-party websites to access some specific information they store in another service provider, rather than all content. The current version is 2.0
OAuth2
OAuth 2.0 authorization framework enables third-party applications to obtain limited access to HTTP services, approve the interaction between resource owners and HTTP services on behalf of resource owners, or allow third-party applications to obtain access on behalf of themselves. This specification replaces and discards OAuth 1.0 protocol [excerpted from RFC 6749]
In short, OAuth2 is an authorization framework used to control the access rights of third-party applications to HTTP service resources.
Why OAuth2?
Problem scenario:
We surf a website / forum and find that the website has a good content that we want to collect. Click collect and find that we need to log in to the website for authorization. The website provides several login methods. The first is to log in with the account of the website, and the second is to log in automatically with applications such as QQ, wechat and microblog. We don't want to register the account of the website manually, Therefore, if you choose the second method to log in, you will jump to the third-party application authorization page. At this time, you will face two authorization verification modes
Traditional mode
In the traditional client server authentication model, the client requests access to restricted resources (protected resources), and the server authenticates by using the server of the resource owner. In order to provide third-party applications with access to restricted resources, resource owners must share credentials with them [from RFC 6749]
In other words, for QQ, wechat or microblog, the website we browse belongs to a third-party application, and authorization actually allows the other party to access the resources of QQ and other resource owners. The traditional authorization mode is equivalent to giving the other party the account code for us to log in to QQ, wechat or microblog. The third party can obtain the information of QQ and other resource owners through the account password, It is used to create your own account for other operations.
Disadvantages:
- Third party applications are required to store the credentials of the resource owner for future use, usually password plaintext.
- The server still needs to support password authentication, which is an inherent security vulnerability in passwords.
- The third-party application obtains excessively extensive access to the protected resources of the resource owner, which makes the resource owner unable to control the scope and time limit for the third-party application to access the limited subset of resources of the resource owner.
- The resource owner cannot revoke access to a single third party without affecting other third parties. It can only do so by changing the password of the third party.
- The disclosure of any third-party application leads to the disclosure of the end user's password and all data protected by the password.
OAuth2 mode
OAuth solves these problems by introducing the authorization layer and separating the client role from the resource owner role. In OAuth, the client requests access to resources controlled by the resource owner and hosted on the resource server, and grants a set of credentials different from those owned by the resource owner. [from RFC 6749]
The above description can be understood as OAuth2 replacing the long-term credentials that can pass freely, such as the plaintext password saved by the traditional third-party application, with temporary controllable credentials, which determines the limited permissions and validity time of the third-party application with the credentials. In this way, the problem of the above traditional model is solved.
The specific principle is shown below
Principle of Spring Security OAuth2
Role of OAuth2
Resource Owner
An entity that can grant access to protected resources. When the resource owner is an individual, it is called an end user [QQ / wechat / microblog in the above example]
Resource server
The server hosting protected resources can receive and respond to requests for protected resources using access tokens [QQ / wechat / microblog server in the above example]
Client
An application that uses the authorization of the resource owner to initiate requests for protected resources on behalf of the resource owner [the website of the above example]
Authorization server
Issue an access token to the client's server after the resource owner is successfully verified and authorized
OAuth2 processing flow
- (A) The client requests authorization from the resource owner. The authorization request can be made directly to the resource owner (as shown in the figure) or indirectly through the authorization server as an intermediary (the preferred scheme).
- (B) The resource owner allows authorization and returns the certificate (authorization permission) (such as code).
- (C) The client authenticates through the authorization server, provides authorization credentials (such as code) and requests access tokens.
- (D) The authorization server authenticates the client
- (E) the client requests protected resources from the resource server and authenticates by providing an access token.
- (F) The resource server verifies the access token and returns the protected resource if it is correct.
In short, the client applies to the resource owner for authorization and obtains the authorization certificate after authorization. The authorization certificate can be used to request an access token from the authorization server. After obtaining the access token, the token can be used to temporarily access the resources within the permission of the resource server. After the token is valid, the token will become invalid.
In the spring cloud microservice scenario, each service may be a client or a resource server.
License for OAuth2
It can be seen from the above process that if you want to obtain an Access Token, you must first obtain the Authorization Grant of the resource owner. There are four types of Authorization Grant in OAuth2. Different authorization types determine the operation mode of authorization allowed by the resource owner:
Authorization code
Authorization code mode is the most widely used authorization mode. The authorization code is obtained by using the authorization server as the intermediary between the client and the resource owner. The client does not directly request authorization from the resource owner, but guides the resource owner to the authorization server. After that, the authorization server guides the resource owner to return to the client with the authorization code. Therefore, the credentials of the resource owner are not shared with the client, only the authorization code is shared, which is safe and anti exposure
Implicit authorization (simplified authorization)
Instead of receiving the authorization code, the client is directly issued with an access token, which can be said to be a simplified version of the authorization type, which can improve the response speed of the client because it reduces the number of round trips to obtain the access token.
Resource owner password credentials
The password credentials of the resource owner (i.e. user name and password) can be directly used as the authorization permission to obtain the access token. However, the resource owner credential is only used for one request and exchanged as an access token. If the access token fails, you only need to refresh the token. The client will not save the credential, which is different from the traditional mode.
Client credentials
In this mode, authorization is not accessed by the user, but directly registered with the client. The client authenticates the resource owner in its own name and requires resources.
Access token for OAuth2
An access token is a credential used to access a protected resource. An access token is a string representing the authorization issued to the client.
This string is usually opaque to the client.
The token represents the specific scope and duration of access rights licensed by the resource owner and implemented by the resource server and authorization server.
Refresh token for OAuth2 (note that refresh token is a noun, not an action)
The refresh token is the credential used to obtain the access token.
The refresh token is issued by the authorization server to the client to obtain a new access token [instead of obtaining the authorization permission again] when the current access token expires or expires, or to obtain an additional access token with an equal or narrower range (the access token may have a shorter life cycle and fewer permissions than those authorized by the resource owner).
Based on the analysis of OAuth2 source code, the overall authentication and authorization process is shown in the figure below:
Practice integrating Spring Security OAuth2
Project preparation
Based on the previous article, this paper integrates the authentication system, which naturally involves the problems of users and permissions. Therefore, a complete user system should be constructed. The following are the database tables and related records used:
-- ---------------------------- -- Table structure for sys_menu Menu table -- ---------------------------- DROP TABLE IF EXISTS `sys_menu`; CREATE TABLE `sys_menu` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Primary key', `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'Menu coding', `p_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'Menu parent code', `p_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'Parent menu ID', `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'name', `url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'Request address', `is_menu` int(11) NULL DEFAULT NULL COMMENT 'Is it a menu(1.Menu. two.Button)', `level` int(11) NULL DEFAULT NULL COMMENT 'Menu level', `sort` int(11) NULL DEFAULT NULL COMMENT 'Menu sorting', `status` int(11) NULL DEFAULT NULL COMMENT 'Menu status (active or not)', `del_flag` int(255) NULL DEFAULT NULL COMMENT 'Delete', `icon` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'Menu icon', `create_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'creator', `update_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'Last updated by', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Creation time', `update_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT 'Last update time', PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `FK_CODE`(`code`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact; SET FOREIGN_KEY_CHECKS = 1; -- ---------------------------- -- Records of sys_menu -- ---------------------------- -- ---------------------------- -- Table structure for sys_privilege Role permission table -- ---------------------------- DROP TABLE IF EXISTS `sys_privilege`; CREATE TABLE `sys_privilege` ( `role_id` int(11) NOT NULL COMMENT 'role id', `menu_id` int(11) NOT NULL COMMENT 'menu id', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Creation time', PRIMARY KEY (`role_id`, `menu_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact; SET FOREIGN_KEY_CHECKS = 1; -- ---------------------------- -- Records of sys_privilege -- ---------------------------- -- ---------------------------- -- Table structure for sys_role Role table -- ---------------------------- DROP TABLE IF EXISTS `sys_role`; CREATE TABLE `sys_role` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Primary key', `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'Role name', `value` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'Role value', `tips` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'Role description', `create_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'creator', `update_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'Last updated by', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Creation time', `update_time` timestamp NULL DEFAULT '0000-00-00 00:00:00' COMMENT 'Last update time', `status` int(11) NULL DEFAULT NULL COMMENT 'Status (available)', `del_flag` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'Delete', PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `unique_role_name`(`name`) USING BTREE, UNIQUE INDEX `unique_role_value`(`value`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 18 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact; SET FOREIGN_KEY_CHECKS = 1; -- ---------------------------- -- Records of sys_role -- ---------------------------- INSERT INTO `sys_role`(`id`, `name`, `value`, `tips`, `create_by`, `update_by`, `create_time`, `update_time`, `status`, `del_flag`) VALUES (6, 'administrators', 'admin', NULL, 'admin', 'admin', '2019-06-13 19:02:52', '2017-06-26 12:46:09', 1, '0'); INSERT INTO `sys_role`(`id`, `name`, `value`, `tips`, `create_by`, `update_by`, `create_time`, `update_time`, `status`, `del_flag`) VALUES (8, 'Super administrator', 'super', NULL, 'admin', 'admin', '2019-06-13 19:02:52', '2019-06-05 10:55:23', 1, '0'); INSERT INTO `sys_role`(`id`, `name`, `value`, `tips`, `create_by`, `update_by`, `create_time`, `update_time`, `status`, `del_flag`) VALUES (17, 'user', 'user', NULL, 'admin', 'admin', '2019-06-13 19:02:59', '2017-07-21 09:41:28', 1, '0'); -- ---------------------------- -- Table structure for sys_user User table -- ---------------------------- DROP TABLE IF EXISTS `sys_user`; CREATE TABLE `sys_user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Primary key', `username` varchar(45) NOT NULL COMMENT 'user name', `password` varchar(96) NOT NULL COMMENT 'password', `truename` varchar(45) DEFAULT NULL COMMENT 'Real name', `birthday` TIMESTAMP COMMENT 'birthday', `sex` int(11) DEFAULT NULL COMMENT 'Gender (0 male and 1 female)', `email` varchar(45) DEFAULT NULL COMMENT 'mailbox', `phone` varchar(45) DEFAULT NULL COMMENT 'cell-phone number', `status` int(11) DEFAULT NULL COMMENT 'Account status (activated or not)', `del_flag` int(10) DEFAULT NULL COMMENT 'Delete', `create_time` TIMESTAMP COMMENT 'Creation time', `update_time` TIMESTAMP COMMENT 'Last update time', `avatar` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `unique_user_username` (`username`) ) ENGINE=InnoDB AUTO_INCREMENT=52 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of sys_user The super administrator and administrator password are 123456 -- ---------------------------- INSERT INTO `sys_user`(`id`, `username`, `password`, `truename`, `birthday`, `sex`, `email`, `phone`, `status`, `del_flag`, `create_time`, `update_time`) VALUES (46, 'super', '$2a$10$cKRbR9IJktfmKmf/wShyo.5.J8IxO/7YVn8twuWFtvPgruAF8gtKq', 'Super administrator', '2019-06-13 18:44:36', 1, NULL, NULL, 1, 0, '2019-06-13 18:44:36', '2019-06-13 18:44:36'); INSERT INTO `sys_user`(`id`, `username`, `password`, `truename`, `birthday`, `sex`, `email`, `phone`, `status`, `del_flag`, `create_time`, `update_time`) VALUES (48, 'admin', '$2a$10$cKRbR9IJktfmKmf/wShyo.5.J8IxO/7YVn8twuWFtvPgruAF8gtKq', 'administrators', '2019-06-13 18:44:36', 1, NULL, NULL, 1, 0, '2019-06-13 18:44:36', '2019-06-13 18:44:36'); -- ---------------------------- -- Table structure for sys_user_role User role table -- ---------------------------- DROP TABLE IF EXISTS `sys_user_role`; CREATE TABLE `sys_user_role` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Primary key', `user_id` int(11) NULL DEFAULT NULL COMMENT 'user id', `role_id` int(11) NULL DEFAULT NULL COMMENT 'role id', `create_time` datetime NULL DEFAULT NULL COMMENT 'Creation time', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact; SET FOREIGN_KEY_CHECKS = 1; -- ---------------------------- -- Records of sys_user_role -- ---------------------------- -- ---------------------------- -- Table structure for oauth_client_details Authentication client information -- ---------------------------- DROP TABLE IF EXISTS `oauth_client_details`; CREATE TABLE `oauth_client_details` ( `client_id` varchar(48) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'terminal id', `resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'resources id', `client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'Terminal password', `scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'Terminal domain', `authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'Authentication authorization type', `web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'Network redirection uri', `authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'authenticator', `access_token_validity` int(11) NULL DEFAULT NULL COMMENT 'AccessToken term of validity', `refresh_token_validity` int(11) NULL DEFAULT NULL COMMENT 'RefreshToken term of validity', `additional_information` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'Additional data', `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 = Compact; SET FOREIGN_KEY_CHECKS = 1; -- ---------------------------- -- Records of oauth_client_details webApp The password is 123456 -- ---------------------------- INSERT INTO `oauth_client_details`(`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('app', NULL, 'app', 'app', 'password,refresh_token', NULL, NULL, NULL, NULL, NULL, NULL); INSERT INTO `oauth_client_details`(`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('webApp', NULL, '$2a$10$cKRbR9IJktfmKmf/wShyo.5.J8IxO/7YVn8twuWFtvPgruAF8gtKq', 'app', 'authorization_code,password,refresh_token,client_credentials', NULL, NULL, NULL, NULL, NULL, NULL);
1, Construction project
1. Create a common-auth2 authentication module. See the project link at the bottom for specific dependencies, classes and methods.
2. Create basic user, role and menu information as a project, system API. See the project link at the bottom for specific dependencies, classes and methods.
3. The operation of users, roles and menus is a project, system handle. See the links at the bottom for specific items and dependencies.
2, Create authentication module project
Create the spring cloud security oauth2 project module as the authentication and authorization server:
pom dependencies are as follows
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>spring-cloud-redis</artifactId> <groupId>com.redis</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>com.security</groupId> <artifactId>spring-cloud-security-OAuth2</artifactId> <version>1.0-SNAPSHOT</version> <description>Authentication server</description> <dependencies> <!--Security Basic module dependency--> <dependency> <groupId>com.springcloud</groupId> <artifactId>common-auth2</artifactId> </dependency> <!--eureka client--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!--Configure client--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency> </dependencies> </project>
1. Configure authentication server
Add configuration, use jdbc to store user information and terminal information, and redis database to store token mode
Here we have customized several special points. Combined with the above flow chart, we know:
UserDetailsService is an operation class used by OAuth2 to obtain user information. Here, we rewrite this class to obtain user information from the database and store the user information in redis cache:
/** * @description The user information service implements the UserDetailsService interface method of Spring Security for identity authentication * @auther: xukang * @date: 2021-06-04 15:39 */ @Slf4j @Component public class UserDetailServiceImpl implements UserDetailsService { //User information service @Autowired private SysUserService userService; //Role information service @Autowired private SysRoleService roleService; //User permission information service @Autowired private SysPermissionService permissionService; //The redis tool class dedicated to login users encapsulates the user's basic information operations @Autowired private UserHelper userHelper; //redis tool class @Autowired private RedisHelper redisHelper; /** * Find the account information according to the user name and return the user information entity * @param username user name * @return UserDetails user information entity for authentication * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { Set<GrantedAuthority> grantedAuthorities = new HashSet<>(); boolean enabled = true; // Availability: true: available false: not available boolean accountNonExpired = true; // Expiration: true: no expiration; false: expiration boolean credentialsNonExpired = true; // Validity: true: voucher valid false: voucher invalid boolean accountNonLocked = true; // Lockability: true: unlocked false: locked //Judge whether the user information with the user name as the key exists in the cache. If so, it will be directly encapsulated and returned if(StrUtil.isNotBlank(redisHelper.get(username))){ SysUser sysUser = JSONUtil.toBean(redisHelper.get(username), SysUser.class); return new User(sysUser.getUsername(),sysUser.getPassword(), enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, grantedAuthorities); } //Query and obtain the account information from the database SysUser sysUser = this.userService.findByUsername(username); if (ObjectUtil.isNull(sysUser)) { throw new UsernameNotFoundException("user:" + username + ",non-existent!"); } //Get user role list List<SysRole> roles = this.roleService.getRoleByUserId(sysUser.getId()); if (!CollUtil.isEmpty(roles)){ for (SysRole role:roles){ //ROLE must be ROLE_ At the beginning, it can be set in the database GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_"+role.getValue()); grantedAuthorities.add(grantedAuthority); //Get user permission list List<SysMenu> permissions = this.permissionService.getPermissionsByRoleId(role.getId()); if (!CollUtil.isEmpty(permissions)){ for (SysMenu menu:permissions) { GrantedAuthority authority = new SimpleGrantedAuthority(menu.getCode()); grantedAuthorities.add(authority); } } } } //Save the current user into redis cache userHelper.addUser(sysUser); //Encapsulation return return new User(sysUser.getUsername(),sysUser.getPassword(), enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, grantedAuthorities); } }
TokenEnhancer means token enhancement. It can add information to the original token, so that we can get more information through the token
package com.springcloud.demo.component; import org.springframework.security.core.userdetails.User; import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.token.TokenEnhancer; import java.util.HashMap; import java.util.Map; /** * @description Token enhancer. You can get more information through token [temporarily unavailable] * @auther: xukang * @date: 2021-06-13 10:50 */ public class CustomTokenEnhancer implements TokenEnhancer { @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { User user = (User) authentication.getPrincipal(); final Map<String, Object> additionalInfo = new HashMap<>(); // additionalInfo.put("customInfo", "some_stuff_here"); // Pay attention to the additional information added. It is better not to have the same name as the key in the existing json object, which is prone to errors additionalInfo.put("username", user.getUsername()); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo); return accessToken; } }
Real authentication and authorization server configuration: inherit the authorization server configureradapter, and use @ EnableAuthorizationServer to start the authentication and authorization service
/** * @description Authorization server configuration * @EnableAuthorizationServer Enable authorization services * @auther: xukang * @date: 2021-06-04 15:19 */ @Configuration @EnableAuthorizationServer public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; // Authentication manager @Autowired private DataSource dataSource; //Database connection @Autowired private UserDetailServiceImpl userDetailsService; //User information processor @Autowired private RedisConnectionFactory redisConnectionFactory; // redis connection factory /** * Declare TokenStore implementation description token store object * There are four implementations: inmemorytokenstore (memory), JdbcTokenStore (database), RedisTokenStore (redis cache) and JwtTokenStore (jwt) * redis Because of its expiration time characteristics, it is particularly suitable for storing tokens * @return */ @Bean(name = "tokenStore") TokenStore tokenStore(){ RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory); /** * Set the prefix of token data stored in redis */ // redisTokenStore.setPrefix(SecurityConst.PROJECT_PREFIX+SecurityConst.OAUTH_PREFIX); return redisTokenStore; } /** * Set client details processor * Configure client detailsservice * Client details are initialized here * Store and retrieve the detailed information through the database * @param clients * @throws Exception */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.withClientDetails(clientDetails()); } /** * Configure authorization, access endpoint of token and token services * @param endpoints Terminal object * @throws Exception */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints){ endpoints.tokenStore(tokenStore()) //Set token store object .userDetailsService(userDetailsService) //Set up user information processor .authenticationManager(authenticationManager) //Set authentication processor .tokenEnhancer(new CustomTokenEnhancer()) //token enhancement .reuseRefreshTokens(true) //This field sets whether the refresh token is reused, that is, whether to retain the original refresh without re obtaining it by refreshing the token. true:reuse;false:no reuse. .exceptionTranslator(new MssWebResponseExceptionTranslator());//Authentication exception conversion // To solve the problem of obtaining token concurrency DefaultTokenServices tokenServices = new DefaultTokenServices(); tokenServices.setTokenStore(tokenStore()); //Set token store object tokenServices.setTokenEnhancer(new CustomTokenEnhancer()); //Set token enhancement tokenServices.setAuthenticationManager(authenticationManager); // Set token processor tokenServices.setSupportRefreshToken(true); //Support refresh token tokenServices.setClientDetailsService(clientDetails()); //Set up terminal management tokenServices.setAccessTokenValiditySeconds(SecurityConst.ACCESS_TOKEN_VALIDITY_SECONDS); // The validity period of the token is set by user. The default is 12 hours tokenServices.setRefreshTokenValiditySeconds(SecurityConst.REFRESH_TOKEN_VALIDITY_SECONDS);//The default is 30 days, which can be modified here endpoints.tokenServices(tokenServices); } /** * Configure the security constraints of the token endpoint * * @param security security * @throws Exception */ @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.tokenKeyAccess("permitAll()"); security .checkTokenAccess("isAuthenticated()"); security.allowFormAuthenticationForClients(); //Allow form authentication submission } /** * Declare ClientDetails implementation * There are two implementations: JdbcClientDetailsService [client information exists in database] and InMemoryClientDetailsService [client information exists in memory] * redis can be customized here, and jdbc is tentatively used * @return */ @Bean public ClientDetailsService clientDetails() { JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource); return jdbcClientDetailsService; } /** * Register the Token processor as the default implementation of the operation token(crud) in oauth2 * <p>Note that when customizing TokenServices, you need to set @ Primary, otherwise there will be three beans of the same type, which cannot be injected with an error</p> * @return */ // @Primary // @Bean // public DefaultTokenServices defaultTokenServices(){ // DefaultTokenServices tokenServices = new DefaultTokenServices(); tokenServices.setTokenStore(tokenStore()); //Set token store object tokenServices.setTokenEnhancer(tokenEnhancer()); //Set token enhancement tokenServices.setAuthenticationManager(authenticationManager); // Set token processor tokenServices.setSupportRefreshToken(true); //Support refresh token tokenServices.setClientDetailsService(clientDetails()); //Set up terminal management tokenServices.setAccessTokenValiditySeconds(60*60*12); // The validity period of the token is set by user. The default is 12 hours tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);//The default is 30 days, which can be modified here // return tokenServices; // } }
2. Configure security service: inherit WebSecurityConfigurerAdapter
package com.springcloud.demo.config; import com.springcloud.demo.service.impl.UserDetailServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; 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.UserDetailsByNameServiceWrapper; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider; import org.springframework.security.web.csrf.CookieCsrfTokenRepository; /** * @description Security configuration * @EnableWebSecurity Enable web security configuration * @EnableGlobalMethodSecurity With global method security annotations enabled, you can use annotations on methods to filter requests * @auther: xukang * @date: 2021-06-04 15:25 */ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { /** * Inject user information service * @return User information service object */ @Autowired private UserDetailServiceImpl userDetailsService; @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /** * Certification management * @return Authentication management object * @throws Exception Authentication exception information */ @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } /** * Global user information and password * @param auth Certification management * @throws Exception User authentication exception information */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //Set PreAuthenticatedAuthenticationProvider, otherwise refreshing the token will result in "No AuthenticationProvider found for org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken" PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider = new PreAuthenticatedAuthenticationProvider(); //Set userDetailService, otherwise NullPointException will be reported when refreshing token preAuthenticatedAuthenticationProvider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<>(userDetailsService)); auth.authenticationProvider(preAuthenticatedAuthenticationProvider) .userDetailsService(userDetailsService) //Set user information operation Service .passwordEncoder(passwordEncoder()); //Set user password parser } /** * http Security configuration * @param http http Security object * @throws Exception http Security exception information */ @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().fullyAuthenticated() .antMatchers("/oauth/token").permitAll() //No token is required to access resources directly .and() .csrf().disable(); http // Header cache .headers() .cacheControl() .and() // Prevent websites from being nested .frameOptions() .sameOrigin() .and() .csrf() .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .and() // Cross domain support .cors(); http .requestMatchers() //Accepted requests .antMatchers("/login", "/logout", "/oauth/authorize", "/oauth/confirm_access") .and() .authorizeRequests()// Endpoint exclusion .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .failureUrl("/login?error") .permitAll() .and() .logout() .logoutUrl("/logout") .invalidateHttpSession(true).clearAuthentication(true); } /** * Do not intercept resource requests * @param web * @throws Exception */ @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/css/**", "/js/**", "/plugins/**", "/favicon.ico"); } }
3. Configure resource server
Since the resource server is all services that may be accessed, all services that may be accessed need to be configured, so we configure it in the common-auth2 module
package com.springcloud.demo.config; import cn.hutool.core.util.StrUtil; import com.springcloud.demo.util.OAuthUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; 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; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore; import org.springframework.security.web.util.matcher.RequestMatcher; import javax.servlet.http.HttpServletRequest; /** * @description Resource service configuration * @EnableResourceServer Enable resource services * @auther: xukang * @date: 2021-06-04 15:22 */ @Configuration @EnableResourceServer public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter { @Autowired private RedisConnectionFactory redisConnectionFactory; // redis connection factory /** * And http security configuration * @param http * @throws Exception */ @Override public void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/v2/api-docs/**", "/validatorUrl","/valid","/actuator/**") .permitAll() //Matching does not require a resource authentication path .anyRequest().authenticated() .and() .httpBasic(); } /** * Resource security configuration * @param resources * @throws Exception */ @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { super.configure(resources); //Set the processor in case of access error. The processor automatically accesses and refreshes the token to renew the implicit token // resources.authenticationEntryPoint(new CustomAuthorizationEntryPoint()); //If it is not set here, the authentication of the resource server will get data from InMemoryTokenStore by default resources.tokenStore(new RedisTokenStore(redisConnectionFactory)); } /** * Define OAuth2 request matcher */ private static class OAuth2RequestedMatcher implements RequestMatcher { @Override public boolean matches(HttpServletRequest request) { String auth = request.getHeader("Authorization"); //Judge whether the source request contains oauth2 Authorization information. Here, the Authorization information source may be that the Authorization value of the header starts with Bearer, or the request parameter contains access_token parameter. If one of them is satisfied, the matching is successful String token = OAuthUtil.getToken(request); return StrUtil.isBlank(token); } } }
3, Use the authentication authorization server for authentication
configure gateway
- id: spring-cloud-oauth uri: lb://The spring cloud OAuth # gateway is routed to the spring cloud client module and lb points to the internal registration module predicates: #Forwarding predicate, used to set rules for matching routes - Path=/oauth/** #Match by request path filters: - StripPrefix=1
1. Get token
Request authentication and authorization using webApp terminal:
redis is as follows
2. Refresh token
Refreshing a token is equivalent to holding an unexpired refresh_token to obtain a new token. The request is as follows:
4, Using token to access resource server resources
Using the token obtained above, we can access the system handler module resources. At this time, the system handler module is equivalent to the resource server. First, introduce the common security module into the system handler:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>springcloud-system</artifactId> <groupId>com.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>system-handle</artifactId> <description>operation api Module of</description> <dependencies> <!--User basic information module--> <dependency> <groupId>com.springcloud</groupId> <artifactId>system-api</artifactId> </dependency> <!--eureka client--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!--Refresh profile in real time--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--Configure client--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency> <!--As web Project exists--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--oauth2 Basic module of--> <dependency> <groupId>com.springcloud</groupId> <artifactId>common-security</artifactId> </dependency> </dependencies> </project>
Add the @ EnableResourceServer annotation on the startup class SpringCloudSystemApplication of this module to start the resource service
package com.springcloud.demo; import com.springcloud.demo.annotation.EnableMyResourceServer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; /** * @description * @auther: xukang * @date: 2021-06-04 23:41 */ @EnableEurekaClient @SpringBootApplication @EnableResourceServer public class SpringCloudSystemApplication { public static void main(String[] args) { SpringApplication.run(SpringCloudSystemApplication.class); } }
At this time, no token is carried for access, and the results are as follows:
Carry a token to access resources
summary
1. OAuth is an open standard that allows users to provide a token instead of a user name and password to access their data stored in a specific service provider
2. OAuth2 is an authorization framework used to control the access rights of third-party applications to HTTP service resources.
3. OAuth2 has four roles: Resource Owner, Resource server, Client and authorization server
4. There are four types of authorization and license for OAuth2: authorization code, implicit authorization (simplified authorization), resource owner password credentials, and client credentials
5. The access token of OAuth2 is the credential used to access the protected resource. It is a string representing the authorization issued to the client. The refresh token of OAuth2 is the credential used to obtain the access token