SpringBoot 1.9 整合Oauth2.0

1.9 整合Oauth2.0

1.9.1 Oauth2.0介绍

在实践之前我们先来了解下oauth2.0,OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版。OAuth2.0服务提供者实际上分为:“授权服务 Authorization Service ”和“资源服务Resource Service”。Oauth2.0 的运行流程如下图所示:
在这里插入图片描述

*(A)用户打开客户端,客户端要求用户给予授权。
*(B)用户同意给予客户端授权。
*(C)客户端使用上一步获得的授权(一般是Code),向认证服务器申请令牌TOKEN。
*(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
*(E)客户端使用令牌,向资源服务器申请获取资源(用户信息等)。
*(F)资源服务器确认令牌无误,同意向客户端开放资源。
图中服务端的三个组件分别为:

  • Resource Owner:即指需要授权访问的资源,比如用户昵称,头像
  • Authorization Server:鉴权服务,核心鉴权逻辑
  • Resource Server:资源服务
    Oauth2.0 定义了五种授权方式:
  • 授权码模式(authorization code)
  • 简化模式(implicit)
  • 密码模式(resource owner password credentials)
  • 客户端模式(client credentials)
  • 扩展模式(Extension)

1.9.2 Oauth2.0授权模式

1.9.2.1 授权码模式(authorization_code)

授权码模式是功能最完整、流程最严密的授权模式。他的特点是通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动。以微信公众平台公众号网页应用开发流程为例。
在这里插入图片描述

步骤如下:

  • (A)用户访问客户端,客户端将用户导向认证服务器。
  • (B)用户选择是否给予客户端授权。
  • (C)若用户给予授权,认证服务器将用户导向客户端指定的"重定向URI"(redirection URI),同时附上授权码code。
  • (D)客户端收到授权码code,附上早先的"重定向URI",向认证服务器申请token。这一步是在客户端的后台的服务器上完成的,对用户不可见。
  • (E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。

授权码模式的几个重要参数如下:

  • response_type:表示授权类型,必选项,此处的值固定为"code"
  • appid:表示客户端的ID,必选项
  • redirect_uri:表示重定向URI,可选项
  • scope:表示申请的权限范围,可选项
  • state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。用于防止恶意攻击

授权码模式的URL以及参数应用步骤如下:

  • (1)引导用户跳转到授权页面:
    http://localhost:8080/oauth/authorize?client_id=client&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
    参数:
    • client_id 客户唯一标识
    • redirect_uri 授权后重定向的回调链接地址, 请使用 urlEncode 对链接进行处理
    • response_type 返回类型,请填写code
    • scope 应用授权作用域,有snsapi_base 、snsapi_userinfo 两种
    • state 重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节
  • (2)通过code获取Token
    http://localhost:8080/oauth/token? client_id=client&secret=secret&code=CODE&grant_type=authorization_code
    参数:
    • client_id 客户唯一标识
    • secret 密钥
    • code 填写获取的code参数(存在有效期,通常设为10分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系)
    • grant_type 填写为authorization_code

返回结果:

{
"access_token":"ACCESS_TOKEN", //网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
"expires_in":7200,  // access_token接口调用凭证超时时间,单位(秒)
"refresh_token":"REFRESH_TOKEN", //用户刷新access_token
"client_id":"client",  //用户唯一标识
"scope":"all"  //用户授权的作用域,使用逗号(,)分隔
 } 

  • access_token:表示访问令牌,必选项。
  • token_type:表示令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。
  • expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
  • refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选项。
  • scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。

1.9.2.2 简化模式(implicit)

简化模式是不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过"授权码"步骤。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。

在这里插入图片描述

步骤如下:

  • (A)客户端将用户导向认证服务器。
  • (B)用户决定是否给于客户端授权。
  • (C)若用户授权,认证服务器将用户导向客户端指定的"重定向URI",并在URI的Hash部分包含了访问令牌。
  • (D)浏览器向资源服务器发出请求,其中不包括上一步收到的Hash值。
  • (E)资源服务器返回一个网页,其中包含的代码可以获取Hash值中的令牌。
  • (F)浏览器执行上一步获得的脚本,提取出令牌。
  • (G)浏览器将令牌发给客户端。

下面是上面这些步骤所需要的参数。

扫描二维码关注公众号,回复: 9293361 查看本文章

A步骤中,客户端发出的HTTP请求,包含以下参数:

  • response_type:表示授权类型,此处的值固定为"token",必选项。
  • client_id:表示客户端的ID,必选项。
  • redirect_uri:表示重定向的URI,可选项。
  • scope:表示权限范围,可选项。
  • state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值

例如:


GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz&redirect_uri=http://www.baidu.com 
HTTP/1.1

Host: www.baidu.com

C步骤中,认证服务器回应客户端的URI,包含以下参数:

  • access_token:表示访问令牌,必选项。
  • token_type:表示令牌类型,该值大小写不敏感,必选项。
  • expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
  • scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。
  • state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。
    例如:
HTTP/1.1 302 Found
Location: http://www.baidu.com#access_token=9e64da25-105c-4d20-a8b5-606c553c9b33&token_type=bearer&expires_in=829

认证服务器用HTTP头信息的Location栏,指定浏览器重定向的网址。注意,在这个网址的Hash部分包含了令牌。
根据上面的D步骤,下一步浏览器会访问Location指定的网址,但是Hash部分不会发送。接下来的E步骤,服务提供商的资源服务器发送过来的代码,会提取出Hash中的令牌。

1.9.2.3 密码模式(Password)

向客户端提供自己的用户名和密码,客户端使用这些信息,向"服务商提供商"索要授权。在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或由一个著名公司出品。而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。

步骤如下:

  • (A)用户向客户端提供用户名和密码。
  • (B)客户端将用户名和密码发给认证服务器,向后者请求令牌。
  • (C)认证服务器确认无误后,向客户端提供访问令牌。
    在B步骤中,客户端发出的HTTP请求,包含以下参数:
  • grant_type:表示授权类型,此处的值固定为"password",必选项。
  • username:表示用户名,必选项。
  • password:表示用户的密码,必选项。
  • scope:表示权限范围,可选项。

例如:


     POST /oauth/token HTTP/1.1
     Host: localhost
     Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
     Content-Type: application/x-www-form-urlencoded
     grant_type=password&username=admin&password=111111
   

C步骤中,认证服务器向客户端发送访问令牌,例子:


     HTTP/1.1 200 OK
     Content-Type: application/json;charset=UTF-8
     Cache-Control: no-store
     Pragma: no-cache
     {
       "access_token":"2YotnFZFEjr1zCsicMWpAA",
       "token_type":"example",
       "expires_in":3600,
       "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
       "example_parameter":"example_value"
     }
     

整个过程中,客户端不得保存用户的密码。

1.9.2.4 客户端模式(client_credentials)

客户端模式下,并不存在对个体用户授权的行为,被授权的主体为client。因此,该模式可用于对某类用户进行集体授权。
在这里插入图片描述

申请该模式时,需要在HTTP request entity-body中提交以下信息。

POST /oauth/token
content-type: application/x-www-form-urlencoded
user-agent: PostmanRuntime/7.1.1
accept: */*
host: localhost:8080
accept-encoding: gzip, deflate
content-length: 74
grant_type=client_credentialsscope=allclient_id=zgqclient_secret=secret

若申请成功,服务器将返回access token和token有效时间。


HTTP/1.1 200
status: 200
cache-control: no-store
pragma: no-cache
x-content-type-options: nosniff
x-xss-protection: 1; mode=block
x-frame-options: DENY
content-type: application/json;charset=UTF-8
transfer-encoding: chunked
date: Thu, 20 Jun 2019 09:01:58 GMT
{"access_token":"43f856cb-2976-4a73-85f3-75638c32f8d7","token_type":"bearer","expires_in":1199,"scope":"all"}


1.9.2.5 扩展模式

扩展模式也叫自定义模式。Oauth2.0的规范中要求 “grant type”参数必须为URI。对于其他申请数据,可以根据需求进行自定义。这里不对这个部分做深入讨论,如果需要可以进一步查看Oauth2.0 相关文档。

1.9.2.6 令牌更新

在用户访问的时候,客户端的"访问令牌"如果已经过期,则需要使用"更新令牌"申请一个新的访问令牌。客户端可以发出更新令牌的HTTP请求进行令牌更新。
令牌更新请求包含以下参数:

  • grant_type:表示使用的授权模式,此处的值固定为"refresh_token",必选项。
  • refresh_token:表示早前收到的更新令牌,必选项。
  • scope:表示申请的授权范围,不可以超出上一次申请的范围,如果省略该参数,则表示与上一次一致。
  • client_id: 客户唯一标识
  • client_secret:密钥

例如:
请求内容:

POST /oauth/token
refresh_token=67ced428-1011-4da5-ae54-f3b98cb46b01&grant_type=refresh_token&scope=all&client_id=zgq&token_type=bearer&client_secret=secret

返回内容:

HTTP/1.1 200
{"access_token":"ab827d16-0a6b-4cdd-abaa-ad6535de9881","token_type":"bearer","refresh_token":"67ced428-1011-4da5-ae54-f3b98cb46b01","expires_in":11999,"scope":"all"}

1.9.3 SpringBoot整合Oauth2.0 和Spring Security

1.9.3.1 Step1:创建Oauth2.0所需要的三个表

Mysql脚本如下:

-- ----------------------------
-- 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; 

1.9.3.2 Step2:添加POM配置


<!--安全验证相关-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.0.14.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
 </dependency>

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jwt</artifactId>
</dependency>


1.9.3.3 Step3:配置数据源

在采用数据库存储的Oauth认证信息时需要使用数据源访问数据库。需要在application-dev.properties 文件中增加以下配置。


#DataSource
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver
spring.datasource.url = jdbc:mysql://localhost:3306/splus?serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&characterEncoding=utf-8
spring.datasource.username = root
spring.datasource.password = root
初始化大小,最小,最大
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
# 配置获取连接等待超时的时间
spring.datasource.maxWait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
# 打开PSCache,并且指定每个连接上PSCache的大小
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20 

同时还需要使用注解配置DataSource组件

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;
import java.sql.SQLException;

/**
 * DruidDataSourceConfig
 * Druid数据源管理配置类
 *
 * @author: zone7
 * @time: 2018.08.20
 */
@Configuration
@ConfigurationProperties(prefix = "spring.datasource")
public class DruidDataSourceConfig {
    private String url;
    private String username;
    private String password;
    private String driverClassName;
    private int initialSize;
    private int minIdle;
    private int maxActive;
    private int maxWait;
    private int timeBetweenEvictionRunsMillis;
    private int minEvictableIdleTimeMillis;
    private String validationQuery;
    private boolean testWhileIdle;
    private boolean testOnBorrow;
    private boolean testOnReturn;
    private boolean poolPreparedStatements;
    private int maxPoolPreparedStatementPerConnectionSize;
    private String filters;
    private String connectionProperties;

    // 解决 spring.datasource.filters=stat,wall,log4j 无法正常注册
    @Bean
    @Primary // 在同样的DataSource中,首先使用被标注的DataSource
    public DataSource dataSource() {
        DruidDataSource datasource = new DruidDataSource();
        datasource.setUrl(url);
        datasource.setUsername(username);
        datasource.setPassword(password);
        datasource.setDriverClassName(driverClassName);

        // configuration
        datasource.setInitialSize(initialSize);
        datasource.setMinIdle(minIdle);
        datasource.setMaxActive(maxActive);
        datasource.setMaxWait(maxWait);
        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        datasource.setValidationQuery(validationQuery);
        datasource.setTestWhileIdle(testWhileIdle);
        datasource.setTestOnBorrow(testOnBorrow);
        datasource.setTestOnReturn(testOnReturn);
        datasource.setPoolPreparedStatements(poolPreparedStatements);
        datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
        try {
            datasource.setFilters(filters);
        } catch (SQLException e) {
            System.err.println("druid configuration initialization filter: " + e);
        }
        datasource.setConnectionProperties(connectionProperties);
        return datasource;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getDriverClassName() {
        return driverClassName;
    }

    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }

    public int getInitialSize() {
        return initialSize;
    }

    public void setInitialSize(int initialSize) {
        this.initialSize = initialSize;
    }

    public int getMinIdle() {
        return minIdle;
    }

    public void setMinIdle(int minIdle) {
        this.minIdle = minIdle;
    }

    public int getMaxActive() {
        return maxActive;
    }

    public void setMaxActive(int maxActive) {
        this.maxActive = maxActive;
    }

    public int getMaxWait() {
        return maxWait;
    }

    public void setMaxWait(int maxWait) {
        this.maxWait = maxWait;
    }

    public int getTimeBetweenEvictionRunsMillis() {
        return timeBetweenEvictionRunsMillis;
    }

    public void setTimeBetweenEvictionRunsMillis(int timeBetweenEvictionRunsMillis) {
        this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
    }

    public int getMinEvictableIdleTimeMillis() {
        return minEvictableIdleTimeMillis;
    }

    public void setMinEvictableIdleTimeMillis(int minEvictableIdleTimeMillis) {
        this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
    }

    public String getValidationQuery() {
        return validationQuery;
    }

    public void setValidationQuery(String validationQuery) {
        this.validationQuery = validationQuery;
    }

    public boolean isTestWhileIdle() {
        return testWhileIdle;
    }

    public void setTestWhileIdle(boolean testWhileIdle) {
        this.testWhileIdle = testWhileIdle;
    }

    public boolean isTestOnBorrow() {
        return testOnBorrow;
    }

    public void setTestOnBorrow(boolean testOnBorrow) {
        this.testOnBorrow = testOnBorrow;
    }

    public boolean isTestOnReturn() {
        return testOnReturn;
    }

    public void setTestOnReturn(boolean testOnReturn) {
        this.testOnReturn = testOnReturn;
    }

    public boolean isPoolPreparedStatements() {
        return poolPreparedStatements;
    }

    public void setPoolPreparedStatements(boolean poolPreparedStatements) {
        this.poolPreparedStatements = poolPreparedStatements;
    }

    public int getMaxPoolPreparedStatementPerConnectionSize() {
        return maxPoolPreparedStatementPerConnectionSize;
    }

    public void setMaxPoolPreparedStatementPerConnectionSize(int maxPoolPreparedStatementPerConnectionSize) {
        this.maxPoolPreparedStatementPerConnectionSize = maxPoolPreparedStatementPerConnectionSize;
    }

    public String getFilters() {
        return filters;
    }

    public void setFilters(String filters) {
        this.filters = filters;
    }

    public String getConnectionProperties() {
        return connectionProperties;
    }

    public void setConnectionProperties(String connectionProperties) {
        this.connectionProperties = connectionProperties;
    }
}

1.9.3.4 Step4:新建Spring安全用户明细

用户明细类必须继承org.springframework.security.core.userdetails.User


import com.zone7.admin.sys.pojo.SysUser;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class MyUserDetails extends User{
    private static List grants = Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN"));
    private UserDto user;
 
    public MyUserDetails(UserDto user) {

        super(user.getUsername(), user.getPassword(), true, true, true, true, grants);
        this.user = user;
    }
 
    public UserDto getUser() {
        return user;
    }
 
    public void setUser(UserDto user) {
        this.user = user;
    }
}

1.9.3.5 Step5:新建用户验证服务

用于加载用户信息,当Oauth2.0的 authorization_code或者password授权模式的时候需要使用这个类验证用户信息。


import com.zone7.admin.sys.dto.MyUserDetails;
import com.zone7.admin.sys.dto.UserDto;
import com.zone7.admin.sys.pojo.SysUser;
import com.zone7.admin.sys.service.SysUserService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * @ClassName: UserDetailsServiceImpl
 * @Description: TODO
 * @Author: zgq
 * @Date: 2019/6/19 21:16
 * @Version: 1.0
 */
@Component
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private SysUserService sysUserService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser user = sysUserService.findByKeyword(username);
        if(user==null){
            throw new UsernameNotFoundException("用户不存在!");
        }else{
            return UserDetailConverter.convert(user);
        } 
    }

    private static class UserDetailConverter {
        static UserDetails convert(SysUser user) {
            UserDto dto=new UserDto();
            BeanUtils.copyProperties(user,dto);
            return new MyUserDetails(dto);
        }
    }
}

1.9.3.6 Step6:新增密钥编码解码器

主要用于密码验证


import com.zone7.admin.utils.SHA256Util;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * 密钥编码器
 * @Authod zone7
 */
public class Sha256PasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence charSequence) {
        return SHA256Util.getSHA256StrJava(charSequence.toString());
    }

    @Override
    public boolean matches(CharSequence charSequence, String s) {

        return s.equals(SHA256Util.getSHA256StrJava(charSequence.toString()));
    }
}

1.9.3.7 Step7:新增配置类

  • (1) Oauth2.0授权配置类AuthorizationServerConfigurerAdapter

package com.zone7.admin.config.oauth2;

import com.zone7.admin.sys.service.impl.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer;
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 javax.sql.DataSource;
import java.util.concurrent.TimeUnit;

/**
 * 授权服务配置
 */
@Configuration
@EnableAuthorizationServer
@Order(7)
public class OAuth2ServerConfig extends AuthorizationServerConfigurerAdapter {

   @Bean // 声明TokenStore实现
   public TokenStore tokenStore() {
      return new JdbcTokenStore(dataSource);
   }

   @Bean // 声明 ClientDetails实现
   public ClientDetailsService clientDetails() {
      return new JdbcClientDetailsService(dataSource);
   }
   /**
    * 注入authenticationManager
    * 来支持 password grant type
    */
   @Autowired
   private AuthenticationManager authenticationManager;
   @Autowired
   private DataSource dataSource;
   @Autowired
   private TokenStore tokenStore;
   @Autowired
   private ClientDetailsService clientDetails;
   @Autowired
   private UserDetailsServiceImpl userDetailsService;

   @Bean
   @Primary
   public DefaultTokenServices tokenServices() {
      DefaultTokenServices tokenServices = new DefaultTokenServices();
      tokenServices.setSupportRefreshToken(true);
      tokenServices.setTokenStore(tokenStore);
      return tokenServices;
   }


   @Override
   public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
      oauthServer
            .passwordEncoder(new MyPasswordEncoder())
            .tokenKeyAccess("permitAll()") //url:/oauth/token_key,exposes public key for token verification if using JWT tokens
            .checkTokenAccess("isAuthenticated()") //url:/oauth/check_token allow check token
            .allowFormAuthenticationForClients();
   }




   @Override
   public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
      endpoints.authenticationManager(authenticationManager);
      endpoints.tokenStore(tokenStore());
      endpoints.userDetailsService(userDetailsService);
      endpoints.setClientDetailsService(clientDetails);

      //配置TokenServices参数
      DefaultTokenServices tokenServices = new DefaultTokenServices();
      tokenServices.setTokenStore(endpoints.getTokenStore());
      tokenServices.setSupportRefreshToken(true);
      tokenServices.setClientDetailsService(endpoints.getClientDetailsService());

      tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
      tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1)); // 1天
      endpoints.tokenServices(tokenServices);
   }



   @Override
   public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
      clients.jdbc(dataSource).passwordEncoder(new MyPasswordEncoder());


   }
   
}
 

  • (2) 资源配置类ResourceServerConfigurerAdapter

import org.springframework.context.annotation.Configuration;
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;

/**
 * 资源服务配置
 */
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

   @Override
   public void configure(HttpSecurity http) throws Exception {
      http.requestMatchers().antMatchers("/api/**")
            .and()
            .authorizeRequests().antMatchers("/api/**").authenticated();
   }
   
   @Override
   public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
      resources.resourceId("resourcesId").stateless(true);
   }
   
}

  • (3) WEB安全配置类WebSecurityConfigurerAdapter

import com.zone7.admin.sys.service.impl.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;
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;

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
@Order(100)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

   @Override
   protected void configure(HttpSecurity http) throws Exception {
      http.csrf().disable();
      http.anonymous().disable();

      http.requestMatchers().antMatchers("/oauth/**","/login/**","/logout/**")
            .and()
            .authorizeRequests()
            .antMatchers("/oauth/**","/login/**","/logout/**").authenticated()
            .and()

            .formLogin().permitAll();


   }

   @Override
   public void configure(WebSecurity web) throws Exception {
      //设置静态资源不要拦截
      web.ignoring().antMatchers("/js/**","/cs/**","/images/**");
   }
   @Autowired
   private UserDetailsServiceImpl userDetailsService;

   @Override
   protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      auth.userDetailsService(userDetailsService).passwordEncoder(new Sha256PasswordEncoder());

//    auth.inMemoryAuthentication().passwordEncoder(new Sha256PasswordEncoder())
//          .withUser("admin").password("123456").roles("ADMIN","USER").and()
//          .withUser("user").password("111").roles("USER");
   }

   /**
    * 需要配置这个支持password模式 support password grant type
    * @return
    * @throws Exception
    */
   @Override
   @Bean
   public AuthenticationManager authenticationManagerBean() throws Exception {
      return super.authenticationManagerBean();
   }

}

1.9.3.8 Step8:测试

启动数据和应用程序,使用Postman
首先添加一个http请求,请求地址为资源配置类中配置的路径,例如:
在这里插入图片描述
点击Get New Access Token 按钮。分别使用不同的授权模式测试授权服务器获取令牌的功能。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

点击Request Token 得到Token
postman测试oauth2 get token

发布了15 篇原创文章 · 获赞 0 · 访问量 119

猜你喜欢

转载自blog.csdn.net/u012474395/article/details/104419049
1.9