springSecurity+Oauth2.0之授權模式(客戶端、密碼模式)


SpringSecurity+Oauth2.0之授權模式

 

1: 客戶端模式:

          客戶端模式(Client Credentials Grant)指客戶端以自己的名義而不是以用戶的名義,向"服務提供商"進行認證。嚴格地說,客戶端模式並不屬於OAuth框架所要解決的問題。在這種模式中,用戶直接向客戶端注冊,客戶端以自己的名義要求"服務提供商"提供服務,其實不存在授權問題。

 

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;
————————————————
View Code

 

配置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
View Code

 

配置認證服務器:

/**
 * @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:

(A) 客戶端向認證服務器進行身份認證,並要求一個訪問令牌。請求的參數如下:
  • 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 {

    }
}

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM