SpringSecurity+Oauth2.0之授權模式
1: 客戶端模式:
maven依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.0.6.RELEASE</version>
</dependency>
創建Oauth2.0需要創建三個相關的表,直接使用官方的SQL腳本即可生成(不要修改表名和字段名)
OAuth2 官方的項目中可以找到:https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql

-- ---------------------------- -- Table structure for oauth_access_token -- ---------------------------- DROP TABLE IF EXISTS `oauth_access_token`; CREATE TABLE `oauth_access_token` ( `token_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `token` blob NULL, `authentication_id` varchar(250) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `user_name` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `client_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `authentication` blob NULL, `refresh_token` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, PRIMARY KEY (`authentication_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Table structure for oauth_client_details -- ---------------------------- DROP TABLE IF EXISTS `oauth_client_details`; CREATE TABLE `oauth_client_details` ( `client_id` varchar(250) 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; -- ---------------------------- -- Table structure for oauth_refresh_token -- ---------------------------- DROP TABLE IF EXISTS `oauth_refresh_token`; CREATE TABLE `oauth_refresh_token` ( `token_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `token` blob NULL, `authentication` blob NULL ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; ————————————————
配置yml(這里沒用到數據庫可以不配置):

server: port: 8082 mybatis: mapper-locations: classpath*:mapper/*Mapper.xml configuration: database-id: MySQL # 開啟駝峰轉換 map-underscore-to-camel-case: true # spring boot集成mybatis的方式打印sql # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl spring: application: name: oauth2-security-8082 datasource: druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/my-study?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false username: root password: 123456 filters: stat # 設置最大數據庫連接數,設為0為無限制 maxActive: 20 # 配置初始化大小、最小、最大 initialSize: 1 # 最大等待時間 maxWait: 60000 # 始終保留在池中的最小連接數,如果連接驗證失敗將縮小至此值 minIdle: 1 timeBetweenEvictionRunsMillis: 6000 # 連接在池中保持空閑而不被回收的最小時間(毫秒) minEvictableIdleTimeMillis: 30000 validationQuery: select 'x' # 對池中空閑的連接是否進行驗證,驗證失敗則回收此連接(默認為false) testWhileIdle: true # 當從連接池中取出一個連接時是否進行驗證,若驗證失敗則從池中刪除該連接並嘗試取出另一個連接 testOnBorrow: true # 當一個連接使用完歸還到連接池時是否進行驗證 testOnReturn: false # 啟用游標緩存,這個對數據庫的性能提升很大 poolPreparedStatements: true # 要啟用PSCache,必須配置大於0,當大於0時,poolPreparedStatements自動觸發修改為true。在Druid中,不會存在Oracle下PSCache占用內存過多的問題,可以把這個數值配置大一些,比如說100 maxOpenPreparedStatements: 20 filter: stat: log-slow-sql: true slow-sql-millis: 2000
配置認證服務器:
/** * @Author dw * @ClassName AuthorizationServerConfig * @Description 配置認證服務器 提供/oauth2/authorize,/oauth2/token,/oauth2/check_token,/oauth2/confirm_access,/oauth2/error * @EnableAuthorizationServer // 這個注解告訴 Spring 這個應用是 OAuth2 的授權服務器 * @Date 2020/4/20 14:47 * @Version 1.0 */ @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private DataSource dataSource; @Bean public TokenStore tokenStore() { //使用內存中的 token store // return new InMemoryTokenStore(); //使用Jdbctoken store return new JdbcTokenStore(dataSource); } @Bean public ClientDetailsService clientDetails() { return new JdbcClientDetailsService(dataSource); } /** * @return * @Author dw * @Description 配置token的過期日期等 * @Date 2020/4/23 18:32 * @Param */ @Bean @Primary public DefaultTokenServices defaultTokenServices() { DefaultTokenServices services = new DefaultTokenServices(); //設置Token 20秒過期 services.setAccessTokenValiditySeconds(20); //設置刷新token的過期時間 services.setRefreshTokenValiditySeconds(666); services.setTokenStore(tokenStore()); return services; } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { // 配置客戶端, 用於client認證 clients.withClientDetails(clientDetails()); // 第一次使用的時候,需要配置客戶端信息,或者手動添加客戶端信息到數據庫oauth_client_details表中 // clients.jdbc(dataSource) // .withClient("myClient") // .secret(new BCryptPasswordEncoder().encode("123456")) // .authorizedGrantTypes("password", "refresh_token")//允許授權范圍 // .authorities("ROLE_ADMIN","ROLE_USER")//客戶端可以使用的權限 // .scopes( "read", "write") // .accessTokenValiditySeconds(7200) // .refreshTokenValiditySeconds(7200); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { // endpoints.tokenStore(tokenStore()) endpoints.tokenServices(defaultTokenServices()) //接收GET和POST .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST); } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security .tokenKeyAccess("permitAll()") .checkTokenAccess("isAuthenticated()") .allowFormAuthenticationForClients();//允許表單登錄 } }
如果說是其他模式:
.authorizedGrantTypes("authorization_code","client_credentials","password","refresh_token")//授權方式
"authorization_code" : code模式
"client_credentials": 客戶端模式
"password": 密碼模式
"implicit": 簡化模式
關於設置Token的過期時間如果配置的是從數據庫讀取,上面配置的Token過期時間將無效,可以在數據庫中設置過期時間
配置資源服務器:
/** * @Author dw * @ClassName ResourceServerConfig * @Description 配置資源器 * @EnableResourceServer 這個類表明了此應用是OAuth2 的資源服務器,此處主要指定了受資源服務器保護的資源鏈接 * @Date 2020/4/20 15:03 * @Version 1.0 */ @Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http.csrf().disable() //匹配需要資源認證路徑 .antMatcher("/**") .authorizeRequests() //匹配不需要資源認證路徑 .antMatchers("/oauth/**").permitAll() .anyRequest().authenticated(); } }
配置springSecurity:
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { // 配置密碼的加密方式 @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /** * 用來配置攔截保護的請求 * * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() //匹配需要資源認證路徑 .antMatcher("/**") .authorizeRequests() //匹配不需要資源認證路徑 .antMatchers("/oauth/**").permitAll() .anyRequest().authenticated(); } public static void main(String[] args) { System.out.println(new BCryptPasswordEncoder().encode("123456")); } }
Controller受保護的資源:
/** * @Author dw * @ClassName HelloSecurityController * @Description * @Date 2020/4/14 11:34 * @Version 1.0 */ @RestController public class HelloSecurityController { @GetMapping("/admin/hello") public String admin() { return "hello admin"; } @GetMapping("/user/hello") public String user() { return "hello user"; } @GetMapping("/db/hello") public String db() { return "hello db"; } @GetMapping("/hello") public String hello() { return "hello"; } }
注意:這里可能需要你手動配置你的客戶端到這張表中
獲取token:
- grant_type:表示授權類型,必選項,此處的值固定為"client_credentials"
- client_id:表示客戶端的ID,必選項
- client_secret:客戶端的密碼,可選項
- scope:表示申請的權限范圍,可選項
通過Token訪問受保護的資源:
2: 密碼模式:
這里分成資源服務器與授權服務器,即分離的形式來搭建,而不是資源服務器與授權服務器都在同一個項目中
maven依賴(兩個項目中都要添加):
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
<dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> <version>2.0.6.RELEASE</version> </dependency>
其他的依賴,可能項目中用得上:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--操作數據庫--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- MySQL 驅動 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- Druid 數據庫連接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.22</version> </dependency> <!-- region MyBatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
2.1:首先我們先搭建授權服務:
yml配置:
server: port: 8086 mybatis: mapper-locations: classpath*:mapper/*Mapper.xml configuration: database-id: MySQL # 開啟駝峰轉換 map-underscore-to-camel-case: true # spring boot集成mybatis的方式打印sql # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl spring: application: name: oauth2-security-password-8086 datasource: druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/my-study?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false username: root password: 123456 filters: stat # 設置最大數據庫連接數,設為0為無限制 maxActive: 20 # 配置初始化大小、最小、最大 initialSize: 1 # 最大等待時間 maxWait: 60000 # 始終保留在池中的最小連接數,如果連接驗證失敗將縮小至此值 minIdle: 1 timeBetweenEvictionRunsMillis: 6000 # 連接在池中保持空閑而不被回收的最小時間(毫秒) minEvictableIdleTimeMillis: 30000 validationQuery: select 'x' # 對池中空閑的連接是否進行驗證,驗證失敗則回收此連接(默認為false) testWhileIdle: true # 當從連接池中取出一個連接時是否進行驗證,若驗證失敗則從池中刪除該連接並嘗試取出另一個連接 testOnBorrow: true # 當一個連接使用完歸還到連接池時是否進行驗證 testOnReturn: false # 啟用游標緩存,這個對數據庫的性能提升很大 poolPreparedStatements: true # 要啟用PSCache,必須配置大於0,當大於0時,poolPreparedStatements自動觸發修改為true。在Druid中,不會存在Oracle下PSCache占用內存過多的問題,可以把這個數值配置大一些,比如說100 maxOpenPreparedStatements: 20 filter: stat: log-slow-sql: true slow-sql-millis: 2000
創建表:用戶、角色、權限、用戶角色表、角色權限表。
CREATE TABLE `user` ( `id` int(64) NOT NULL AUTO_INCREMENT, `user_name` varchar(32) DEFAULT NULL, `password` varchar(255) DEFAULT NULL, `enable` tinyint(4) DEFAULT NULL, `locked` tinyint(4) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; CREATE TABLE `role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(32) DEFAULT NULL, `description` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; CREATE TABLE `resources` ( `id` int(11) NOT NULL AUTO_INCREMENT, `pattern` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; CREATE TABLE `user_role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) DEFAULT NULL, `role_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8; CREATE TABLE `role_resource` ( `id` int(11) NOT NULL AUTO_INCREMENT, `role_id` int(11) DEFAULT NULL, `resource_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
創建實體對象:
package com.dw.study.entity; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * (User)實體類 * * @author makejava * @since 2020-04-14 13:10:12 */ public class User implements UserDetails, Serializable { private static final long serialVersionUID = 7673225091735750618L; private Integer id; private String userName; private String password; private boolean enable; private boolean locked; private List<Role> userRoles; @Override public Collection<? extends GrantedAuthority> getAuthorities() { List<SimpleGrantedAuthority> authorities = new ArrayList<>(); for (Role role : userRoles) { authorities.add(new SimpleGrantedAuthority(role.getName())); } return authorities; } @Override public String getUsername() { return userName; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return !locked; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return enable; } public void setPassword(String password) { this.password = password; } @Override public String getPassword() { return password; } public boolean isEnable() { return enable; } public void setEnable(boolean enable) { this.enable = enable; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public boolean isLocked() { return locked; } public void setLocked(boolean locked) { this.locked = locked; } public List<Role> getUserRoles() { return userRoles; } public void setUserRoles(List<Role> userRoles) { this.userRoles = userRoles; } }
public class Role implements Serializable { private static final long serialVersionUID = 825384782616737527L; private Integer id; private String name; private String description; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }
public class Resources implements Serializable { private static final long serialVersionUID = -5840903661920488430L; private Integer id; private String pattern; private List<Role> roles; public List<Role> getRoles() { return roles; } public void setRoles(List<Role> roles) { this.roles = roles; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getPattern() { return pattern; } public void setPattern(String pattern) { this.pattern = pattern; } }
public class UserRole implements Serializable { private static final long serialVersionUID = -33173102844662867L; private Integer id; private Integer userId; private Integer roleId; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } public Integer getRoleId() { return roleId; } public void setRoleId(Integer roleId) { this.roleId = roleId; } }
創建dao
@Repository public interface UserMapperDao { public User loadUserByUsername(String userName); public List<Role> getUserRolesByUid(Integer id); }
創建mapper
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.dw.study.dao.ResourceMapperDao"> <resultMap id="ResourcesMap" type="com.dw.study.entity.Resources"> <id column="id" property="id"/> <result column="pattern" property="pattern"/> <collection property="roles" ofType="com.dw.study.entity.Role"> <id column="roleId" property="id"/> <result column="name" property="name"/> <result column="description" property="description"/> </collection> </resultMap> <select id="getAllResources" resultMap="ResourcesMap"> SELECT r.*, re.id AS roleId, re.`name`, re.description FROM resources AS r LEFT JOIN role_resource AS rr ON r.id = rr.resource_id LEFT JOIN role AS re ON re.id = rr.role_id </select> </mapper>
創建service
@Service public class MyUserDetailServiceImpl implements UserDetailsService { @Autowired private UserMapperDao userMapperDao; @Autowired private PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userMapperDao.loadUserByUsername(username); String encodePassword = passwordEncoder.encode(user.getPassword()); System.out.println("加密后的密碼:" + encodePassword); user.setPassword(encodePassword); if (user == null) { throw new UsernameNotFoundException("賬戶不存在!"); } List<Role> userRoles = userMapperDao.getUserRolesByUid(user.getId()); user.setUserRoles(userRoles); return user; } }
核心:配置認證服務器認證類
package com.dw.study.oauth2Config; import com.dw.study.service.MyUserDetailServiceImpl; import com.dw.study.webConfig.CustomWebResponseExceptionTranslator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper; 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.client.JdbcClientDetailsService; 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 org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider; import javax.sql.DataSource; /** * @Author dw * @ClassName AuthorizationServerConfig * @Description 配置認證服務器 提供/oauth2/authorize,/oauth2/token,/oauth2/check_token,/oauth2/confirm_access,/oauth2/error * @EnableAuthorizationServer // 這個注解告訴 Spring 這個應用是 OAuth2 的授權服務器 * @Date 2020/4/20 14:47 * @Version 1.0 */ @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private DataSource dataSource; // 認證管理器,認證用戶信息 @Autowired private AuthenticationManager authenticationManager; @Autowired private MyUserDetailServiceImpl myUserDetailService; @Autowired private CustomWebResponseExceptionTranslator webResponseExceptionTranslator; @Bean public TokenStore tokenStore() { //使用內存中的 token store // return new InMemoryTokenStore(); //使用Jdbctoken store return new JdbcTokenStore(dataSource); } @Bean public ClientDetailsService clientDetails() { return new JdbcClientDetailsService(dataSource); } /** * @return * @Author dw * @Description 配置token的過期日期等 * @Date 2020/4/23 18:32 * @Param */ // @Bean // @Primary // public DefaultTokenServices defaultTokenServices() { // DefaultTokenServices services = new DefaultTokenServices(); // // access token有效期2個小時 // services.setAccessTokenValiditySeconds(60 * 60 * 2); // // refresh token有效期30天 // services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 30); // // 支持使用refresh token刷新access token // services.setSupportRefreshToken(true); // // 允許重復使用refresh token // services.setReuseRefreshToken(true); //// services.setAuthenticationManager(authenticationManager); // services.setTokenStore(tokenStore()); // return services; // } private PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider() { PreAuthenticatedAuthenticationProvider authenticationProvider = new PreAuthenticatedAuthenticationProvider(); authenticationProvider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper(myUserDetailService)); return authenticationProvider; } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { // 配置客戶端, 用於client認證 clients.withClientDetails(clientDetails()); // 第一次使用的時候,需要配置客戶端信息,或者手動添加客戶端信息到數據庫oauth_client_details表中 // clients.jdbc(dataSource) // .withClient("myClient") // .secret(new BCryptPasswordEncoder().encode("123456")) // .authorizedGrantTypes("password", "refresh_token")//允許授權范圍 // .authorities("ROLE_ADMIN","ROLE_USER")//客戶端可以使用的權限 // .scopes( "read", "write") // .accessTokenValiditySeconds(7200) // .refreshTokenValiditySeconds(7200); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(tokenStore()) // endpoints.tokenServices(defaultTokenServices()) .authenticationManager(authenticationManager) .userDetailsService(myUserDetailService) //接收GET和POST .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST) .exceptionTranslator(webResponseExceptionTranslator); } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security .tokenKeyAccess("permitAll()") .checkTokenAccess("isAuthenticated()") .allowFormAuthenticationForClients();//允許表單登錄 } }
如果不配置token的過期時間,默認是12個小時過期
配置資源
package com.dw.study.oauth2Config; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; /** * @Author dw * @ClassName ResourceServerConfig * @Description 配置資源器 * @EnableResourceServer 這個類表明了此應用是OAuth2 的資源服務器,此處主要指定了受資源服務器保護的資源鏈接 * @Date 2020/4/20 15:03 * @Version 1.0 */ @Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http //定義哪些url需要被保護 哪些不需要保護 .authorizeRequests() //定義這兩個鏈接不需要登錄可訪問 .antMatchers("/oauth/token" , "oauth/check_token").permitAll() //定義所有的都不需要登錄 目前是測試需要 // .antMatchers("/**").permitAll() .anyRequest().authenticated() //其他的都需要登錄 //.antMatchers("/sys/**").hasRole("admin")///sys/**下的請求 需要有admin的角色 .and() .formLogin() // .loginPage("/login") //如果未登錄則跳轉登錄的頁面 這兒可以控制登錄成功和登錄失敗跳轉的頁面 .and() .csrf().disable();//防止跨站請求 spring security中默認開啟 } }
配置springSecurity
package com.dw.study.securityConfig; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; /** * @Author dw * @ClassName WebSecurityConfig * @Description * @Date 2020/4/20 15:07 * @Version 1.0 */ @Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean() ; } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /** * 配置用戶簽名服務 主要是user-details 機制, * * @param auth 簽名管理器構造器,用於構建用戶具體權限控制,這里交給oauth2的AuthorizationServer去處理 * @throws Exception */ // @Override // protected void configure(AuthenticationManagerBuilder auth) throws Exception { // auth.userDetailsService(myUserService) // .passwordEncoder(passwordEncoder()); // } /** * 用來配置攔截保護的請求 * * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { http //定義哪些url需要被保護 哪些不需要保護 .authorizeRequests() //定義這兩個鏈接不需要登錄可訪問 .antMatchers("/oauth/token" , "oauth/check_token").permitAll() //定義所有的都不需要登錄 目前是測試需要 // .antMatchers("/**").permitAll() .anyRequest().authenticated() //其他的都需要登錄 //.antMatchers("/sys/**").hasRole("admin")///sys/**下的請求 需要有admin的角色 .and() .formLogin() // .loginPage("/login") //如果未登錄則跳轉登錄的頁面 這兒可以控制登錄成功和登錄失敗跳轉的頁面 .and() .csrf().disable();//防止跨站請求 spring security中默認開啟 } }
配置全局異常處理類:
package com.dw.study.webConfig; import org.bouncycastle.asn1.ocsp.ResponseData; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator; import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator; import org.springframework.stereotype.Component; /** * @Author dw * @Description web全局異常返回處理器 * @Date 2020/4/24 14:14 * @Param * @return */ @Component public class CustomWebResponseExceptionTranslator extends DefaultWebResponseExceptionTranslator { private static final Logger LOGGER = LoggerFactory.getLogger(CustomWebResponseExceptionTranslator.class); @SuppressWarnings("PMD") @Override
public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
ResponseEntity responseEntity = super.translate(e);
LOGGER.info("statusCode: " + responseEntity.getStatusCodeValue());
OAuth2Exception oAuth2Exception = new OAuth2Exception("出錯了!!");
if(responseEntity.getStatusCodeValue() == 401){
oAuth2Exception.addAdditionalInformation("code", "401");
oAuth2Exception.addAdditionalInformation("data", "");
oAuth2Exception.addAdditionalInformation("message", "用戶名錯誤!");
return new ResponseEntity(oAuth2Exception, HttpStatus.UNAUTHORIZED);
}else if(responseEntity.getStatusCodeValue() == 400){
oAuth2Exception.addAdditionalInformation("code", "400");
oAuth2Exception.addAdditionalInformation("data", "");
oAuth2Exception.addAdditionalInformation("message", "用戶密碼錯誤!");
return new ResponseEntity(oAuth2Exception, HttpStatus.BAD_REQUEST);
}else {
oAuth2Exception.addAdditionalInformation("code", String.valueOf(responseEntity.getStatusCodeValue()));
oAuth2Exception.addAdditionalInformation("data", "");
oAuth2Exception.addAdditionalInformation("message", "認證異常");
return new ResponseEntity(oAuth2Exception, HttpStatus.UNAUTHORIZED);
}
}
}
創建訪問當前登錄用戶的接口:
package com.dw.study.controller; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.security.Principal; /** * @Author dw * @ClassName UserInfoController * @Description * @Date 2020/4/23 15:06 * @Version 1.0 */ @RestController public class UserInfoController { @Value("${server.port}") private String serverPort; @GetMapping(value = "/userInfo") public Principal getUserInfo(Principal principal) { System.out.println("通過端口:" + serverPort + "獲取用戶信息:" + principal); return principal; } }
好的到這里認證服務器就配置完了。
下面配置資源服務器:
1、pom同上;
修改yml:
server:
port: 8087
management:
security:
enabled: false
security: oauth2: #token檢查的授權端url authorization: check-token-access: http://127.0.0.1:8086/oauth/check_token #對應的注冊碼與注冊密碼 client: id: oauth2-password client-secret: 123456 userAuthorizationUri: http://127.0.0.1:8086/oauth/authorize access-token-uri: http://127.0.0.1:8086/oauth/token scope: all grant-type: password # authentication-scheme: form #獲得授權端的當前用戶信息url resource: user-info-uri: http://127.0.0.1:8086/userInfo
定義資源服務器配置:
package com.dw.study.oauth2Config; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; import org.springframework.web.cors.CorsConfiguration; import javax.servlet.http.HttpServletResponse; /** * @Author dw * @ClassName ResourceServerConfig * @Description 配置資源器 * @EnableResourceServer 這個類表明了此應用是OAuth2 的資源服務器,此處主要指定了受資源服務器保護的資源鏈接 * @EnableGlobalMethodSecurity : 開啟三種可以在方法上面加權限控制的注解 * @Date 2020/4/20 15:03 * @Version 1.0 */ @Configuration @EnableResourceServer @EnableOAuth2Client @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true) public class ResourceServerConfig extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http.csrf().disable() //匹配需要資源認證路徑 .antMatcher("/**") .authorizeRequests() //匹配不需要資源認證路徑 .antMatchers("/oauth/**").permitAll() .anyRequest().authenticated(); } }
定義獲取用戶信息的util:
package com.dw.study.Utils; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.provider.OAuth2Authentication; import java.util.LinkedHashMap; /** * @Author dw * @ClassName UserUtil * @Description * @Date 2020/4/24 17:05 * @Version 1.0 */ public class UserUtil { private UserUtil (){} public static LinkedHashMap getUser(){ return getUserDetails(); } private static LinkedHashMap getUserDetails() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); OAuth2Authentication oAuth2Authentication = (OAuth2Authentication)authentication; LinkedHashMap userDetails = (LinkedHashMap) oAuth2Authentication.getUserAuthentication().getDetails(); LinkedHashMap principal = (LinkedHashMap) userDetails.get("principal"); // 這里需要手動傳換成User對象 return principal; } }
最后新建user、role類,同上面一樣
建controller
@RestController @Slf4j public class MyController { @Value("${server.port}") private String serverPort; @GetMapping("/get") public String get(){ return "成功獲取到資源: port: " + serverPort; } @GetMapping("/userInfo") public Object getUserInfo(){ LinkedHashMap user = UserUtil.getUser(); return user; } }
好的,配置完成。下面測試
1:oauth_client_detais這張表中配置我們的客戶端信息
啟動兩個項目,然后使用postman:
1: 獲取token:
2:刷新token獲取token:
訪問用戶信息:
關於使用HTTP請求頭 Authorization,不直接用 client_id 和 client_secret
客戶端發送http請求流程:
服務器發現配置了http auth,於是檢查request里面有沒有"Authorization"的http header
如果有,則判斷Authorization里面的內容是否在用戶列表里面,Authorization header 的典型數據為"Authorization: Basic jdhaHY0=",
其中Basic 表示基礎認證,
jdhaHY0= 是base64編碼的"user:passwd"字符串。
如果沒有,或者用戶密碼不對,則返回http code 401頁面給客戶端。
base64在線編碼j解碼工具:http://tool.chinaz.com/Tools/Base64.aspx
Header:
Authorization : Basic YnJvd3Nlcjo=
配置跨域訪問問題:
/** * 跨域訪問配置 */ @Configuration @Order(Ordered.HIGHEST_PRECEDENCE) public class CORSFilter implements Filter { public static final String CREDENTIALS_NAME = "Access-Control-Allow-Credentials"; public static final String ORIGIN_NAME = "Access-Control-Allow-Origin"; public static final String METHODS_NAME = "Access-Control-Allow-Methods"; public static final String HEADERS_NAME = "Access-Control-Allow-Headers"; public static final String MAX_AGE_NAME = "Access-Control-Max-Age"; @Override public void destroy() { } @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) resp; HttpServletRequest request = (HttpServletRequest) req; response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN"); if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { response.setStatus(HttpServletResponse.SC_OK); } else { chain.doFilter(req, resp); } } @Override public void init(FilterConfig filterConfig) throws ServletException { } }