Spring cloud oauth2搭建OAuth2.0授权服务

我理解的OAuth2.0

首先这是一种授权协议,个人理解这个协议最大的好处是可以使用第三方client进行登录,比如我们在登录csdn博客的时候,可以不必使用用户名密码,直接微信或者qq扫码登录即可。相比与普通的用户名密码登录授权,OAuth2.0多了一个client的概念,client即是你开发的网站,比如要接入微信的登录,你需要在微信网站应用开发创建审核相应的材料,然后获取APPId,此APPID即是client id,通过此client id,即可以使用微信的授权服务,获取token,进行网站的访问。

此处完全是我自己对于OAuth2的理解,如果不对之处,欢迎大佬批评指正。

OAuth2.0的四种授权模式

1. 授权码许可类型(Authorization Code)

2.隐式许可类型(Implicit)

3.客户端凭据许可(Client Credentials)

4.资源拥有者凭据许可(Resource Owner Password Credentials)

在这里我只说一下最复杂的授权码许可类型,其他的三种请参考OAuth 2.0 的四种方式

授权码许可类型是最复杂的授权模式,其流程如下所示。登录客户端需要先获得授权码(authorization_code),然后利用code换取access_token。

代码讲解

总体概述

代码讲解分为两个部分,一个是授权服务,一个是资源服务,资源服务代码主要为了验证生成的access_token能访问相应的资源。

 整个项目的pom文件如下。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.hz.oauth</groupId>
    <artifactId>authority-management-sys-2.0</artifactId>
    <version>1.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
    </parent>

    <modules>
        <module>oauth2-resource</module>
        <module>oauth2-server</module>
    </modules>

    <packaging>pom</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <org.apache.common.lang3.version>3.12.0</org.apache.common.lang3.version>
        <spring-framework.cloud.version>Greenwich.SR4</spring-framework.cloud.version>
        <org.apache.common.beanutils.version>1.9.4</org.apache.common.beanutils.version>
        <com.baomidou.mybatis-plus.version>3.4.3</com.baomidou.mybatis-plus.version>
        <mysql.connector.java.version>8.0.12</mysql.connector.java.version>
        <com.alibaba.fastjson.version>1.2.76</com.alibaba.fastjson.version>

        <!--Lombok-->
        <lombok.version>1.18.10</lombok.version>
        <commons-io.version>2.6</commons-io.version>
        <javadoc.version>3.0.0</javadoc.version>
        <maven-release-plugin.version>2.5.3</maven-release-plugin.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-framework.cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>${org.apache.common.lang3.version}</version>
            </dependency>

            <dependency>
                <groupId>commons-beanutils</groupId>
                <artifactId>commons-beanutils</artifactId>
                <version>${org.apache.common.beanutils.version}</version>
            </dependency>
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${com.baomidou.mybatis-plus.version}</version>
            </dependency>

            <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.connector.java.version}</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>${com.alibaba.fastjson.version}</version>
            </dependency>

        </dependencies>

    </dependencyManagement>


    <build>
        <plugins>
            <!-- for create custom archetype template -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-archetype-plugin</artifactId>
                <version>3.0.1</version>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-release-plugin</artifactId>
                <version>${maven-release-plugin.version}</version>
            </plugin>
        </plugins>


    </build>

</project>

oauth2-server

最为重要的是两个配置文件类,一个是授权服务的配置,一个是security的安全服务配置,security的安全服务配置重要的是把授权服务相应的地址配置允许匿名访问。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <parent>
        <groupId>com.hz.oauth</groupId>
        <artifactId>authority-management-sys-2.0</artifactId>
        <version>1.0</version>
    </parent>

    <artifactId>oauth2-server</artifactId>
    <modelVersion>4.0.0</modelVersion>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- for create custom archetype template -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-archetype-plugin</artifactId>
                <version>3.0.1</version>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-release-plugin</artifactId>
                <version>${maven-release-plugin.version}</version>
            </plugin>
            <plugin>
                <groupId>org.sonarsource.scanner.maven</groupId>
                <artifactId>sonar-maven-plugin</artifactId>
                <version>3.0.2</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/webapp</directory>
                <targetPath>META-INF/resources</targetPath>
                <includes>
                    <include>**/**</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/**</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
                <excludes>
                    <exclude>**/*.jks</exclude>
                </excludes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>false</filtering>
                <includes>
                    <include>**/*.jks</include>
                </includes>
            </resource>
        </resources>


    </build>

</project>

SysAuthorizationConfig

在该类配置中,授权码的保存,用户的授权记录保存在数据库中,其生成数据库代码为

DROP TABLE IF EXISTS `oauth_approvals`;
CREATE TABLE `oauth_approvals` (
  `userId` varchar(256) DEFAULT NULL,
  `clientId` varchar(256) DEFAULT NULL,
  `partnerKey` varchar(32) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `status` varchar(10) DEFAULT NULL,
  `expiresAt` datetime DEFAULT NULL,
  `lastModifiedAt` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

DROP TABLE IF EXISTS `oauth_code`;
CREATE TABLE `oauth_code` (
  `code` varchar(255) DEFAULT NULL,
  `authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

SysAuthorizationConfig类配置代码

/**
 * @program: OAuth-2.0
 * @author: zgr
 * @create: 2021-07-20 11:42
 **/
@Configuration
@EnableAuthorizationServer
public class SysAuthorizationConfig extends AuthorizationServerConfigurerAdapter {


    private final DataSource dataSource;

    private final SysClientService sysClientService;

    private final AuthenticationManager authenticationManager;

    public SysAuthorizationConfig(DataSource dataSource, SysClientService sysClientService, AuthenticationManager authenticationManager) {
        this.dataSource = dataSource;
        this.sysClientService = sysClientService;
        this.authenticationManager = authenticationManager;
    }


    /**
     * 维护一套客户端的信息 即第三方应用软件申请时记录的一些基本信息
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(sysClientService);
    }

    /**
     * 打开验证token的访问权限
     *
     * @param security
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security
                .allowFormAuthenticationForClients()
                .tokenKeyAccess("isAuthenticated()")
                .checkTokenAccess("permitAll()")
                .passwordEncoder(new BCryptPasswordEncoder());
    }


    /**
     * 配置了令牌存储方式为jwt
     * 配置JWT Token的非对称加密来进行签名
     * 配置一个自定义的Token增强器,把更多信息放入Token中
     * 配置使用JDBC数据库方式来保存用户的授权批准记录
     *
     * @param endpoints
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), jwtTokenEnhancer()));

        endpoints.approvalStore(approvalStore())
                .authorizationCodeServices(authorizationCodeServices())
                .tokenEnhancer(tokenEnhancerChain)
                .tokenStore(tokenStore())
                .authenticationManager(authenticationManager);
    }

    /**
     * 使用数据库方式来保存授权码
     *
     * @return
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        return new JdbcAuthorizationCodeServices(dataSource);
    }

    /**
     * 采用jwt方式存储token
     *
     * @return
     */
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtTokenEnhancer());
    }

    /**
     * 使用数据库方式存储用户的批准授权记录
     *
     * @return
     */
    @Bean
    public JdbcApprovalStore approvalStore() {
        return new JdbcApprovalStore(dataSource);
    }

    /**
     * 自定义token增强器
     *
     * @return
     */
    @Bean
    public TokenEnhancer tokenEnhancer() {
        return new SysTokenEnhancer();
    }

    /**
     * jwt 验证
     *
     * @return
     */
    @Bean
    protected JwtAccessTokenConverter jwtTokenEnhancer() {
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        // 设置jwt加解密秘钥,不设置会随机一个
        jwtAccessTokenConverter.setSigningKey(Constant.JWT_SECRET);
        return jwtAccessTokenConverter;
    }
}

SysSecurityConfig

**
 * @program: authority-management-sys-2.0
 * @author: zgr
 * @create: 2021-08-03 09:55
 **/

@Configuration
@EnableWebSecurity
public class SysSecurityConfig extends WebSecurityConfigurerAdapter {


    private final SysUserService sysUserService;
    private final SysLogoutHandler sysLogoutHandler;
    private final SysLogoutSuccessHandler sysLogoutSuccessHandler;


    public SysSecurityConfig(SysUserService sysUserService, SysLogoutHandler sysLogoutHandler, SysLogoutSuccessHandler sysLogoutSuccessHandler) {
        this.sysUserService = sysUserService;
        this.sysLogoutHandler = sysLogoutHandler;
        this.sysLogoutSuccessHandler = sysLogoutSuccessHandler;
    }

    /**
     * 配置认证管理器
     *
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }


    /**
     * 这里是对认证管理器的添加配置,自定义用户详情
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(sysUserService).passwordEncoder(new BCryptPasswordEncoder())
        ;
    }

    //web ignore比较适合配置前端相关的静态资源,它是完全绕过spring security的所有filter的
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/resource");
    }


    /**
     * 安全请求配置,这里配置的是security的部分,这里配置全部通过,安全拦截在资源服务的配置文件中配置,
     * 要不然访问未验证的接口将重定向到登录页面,前后端分离的情况下这样并不友好,无权访问接口返回相关错误信息即可
     *
     * @param http
     * @return void
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .formLogin().loginPage("/login").and()
                .logout().addLogoutHandler(sysLogoutHandler).logoutSuccessHandler(sysLogoutSuccessHandler)
                .and()
                // 由于使用的是JWT,我们这里不需要csrf
                .csrf().disable().cors().and()
                .authorizeRequests()
                .antMatchers("/login", "/oauth/authorize").permitAll()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();

    }
}

客户端服务ClientDetailsService

在SysAuthorizationConfig中,我们配置的是客户端 服务为自定义的sysClientService。实际应用中,通过client id查出对应客户端的信息,查询其拥有的resource资源权限,生成access_token。

/**
     * 维护一套客户端的信息 即第三方应用软件申请时记录的一些基本信息
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(sysClientService);
    }
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SysClientDetails implements ClientDetails {

    private String clientId;

    private Set<String> resourceId;

    private Boolean isSecretRequired;

    private String clientSecret;

    private Boolean isScoped;

    private Set<String> scope;

    private Set<String> authorizedGrantTypes;

    private Set<String> registeredRedirectUri;

    private Integer accessTokenValiditySeconds;

    private Integer refreshTokenValiditySeconds;

    private Boolean isAutoApprove;

    private Map<String, Object> additionalInformation;

    @Override
    public String getClientId() {
        return this.clientId;
    }

    @Override
    public Set<String> getResourceIds() {
        return this.resourceId;
    }

    @Override
    public boolean isSecretRequired() {
        return this.isSecretRequired;
    }

    @Override
    public String getClientSecret() {
        return this.clientSecret;
    }

    @Override
    public boolean isScoped() {
        return this.isScoped;
    }

    @Override
    public Set<String> getScope() {
        return this.scope;
    }

    @Override
    public Set<String> getAuthorizedGrantTypes() {
        return this.authorizedGrantTypes;
    }

    @Override
    public Set<String> getRegisteredRedirectUri() {
        return this.registeredRedirectUri;
    }

    @Override
    public Collection<GrantedAuthority> getAuthorities() {
        return Collections.emptyList();
    }

    @Override
    public Integer getAccessTokenValiditySeconds() {
        return this.accessTokenValiditySeconds;
    }

    @Override
    public Integer getRefreshTokenValiditySeconds() {
        return this.refreshTokenValiditySeconds;
    }

    @Override
    public boolean isAutoApprove(String scope) {
        return this.isAutoApprove;
    }

    @Override
    public Map<String, Object> getAdditionalInformation() {
        return this.additionalInformation;
    }
}


/**
 * @program: authority-management-sys-2.0
 * @author: zgr
 * @create: 2021-08-03 10:12
 **/

@Service
@Slf4j
public class SysClientServiceImpl implements SysClientService {


    @Override
    public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
        log.info("查找客户端id为 {} 的客户端详情", clientId);

        //实际使用中可以从数据库查询客户端详细信息,本例子中直接构造一个客户端

        Set<String> resourceIds = new HashSet<>();
        resourceIds.add("userservice1");

        Set<String> scopes = new HashSet<>(1);
        scopes.add("sever");

        Set<String> grantTypes = new HashSet<>(5);
        grantTypes.add("password");
        grantTypes.add("authorization_code");

        Set<String> registeredRedirectUris = new HashSet<>(1);
        registeredRedirectUris.add("https://baidu.com");

        return SysClientDetails.builder()
                //客户端id
                .clientId("userservice1")
                //拥有的资源id
                .resourceId(resourceIds)
                //是否需要secret
                .isSecretRequired(true)
                //secret加密方式
                .clientSecret(new BCryptPasswordEncoder().encode("1234"))
                //权限范围
                .scope(scopes)
                //支持的授权模式,四种模式支持哪几种
                .authorizedGrantTypes(grantTypes)
                //官方注册的回调地址,这个地址是需要和授权请求的回调地址一致
                .registeredRedirectUri(registeredRedirectUris)
                .isScoped(true)
                //access_token的有效时间
                .accessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(2))
                //refresh_token的有效时间
                .refreshTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30))
                //是否可以自主授权
                .isAutoApprove(true)
                .build();
    }
}

postman演示结果

1.资源有着凭据许可

 完整的返回数据:

{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsidXNlcnNlcnZpY2UxIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2V2ZXIiXSwiZXhwIjoxNjI4NzU3NTMzLCJ1c2VyRGV0YWlscyI6eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInBhc3N3b3JkIjpudWxsLCJyb2xlcyI6W3siaWQiOjEsIm5hbWUiOiJSRUFEIn0seyJpZCI6MSwibmFtZSI6IldSSVRFIn1dLCJhdXRob3JpdGllcyI6W3siYXV0aG9yaXR5IjoiUkVBRCJ9LHsiYXV0aG9yaXR5IjoiV1JJVEUifV0sImVuYWJsZWQiOnRydWUsImNyZWRlbnRpYWxzTm9uRXhwaXJlZCI6dHJ1ZSwiYWNjb3VudE5vbkV4cGlyZWQiOnRydWUsImFjY291bnROb25Mb2NrZWQiOnRydWV9LCJhdXRob3JpdGllcyI6WyJSRUFEIiwiV1JJVEUiXSwianRpIjoiODAwZjgwMmUtZTUzNS00N2RiLTkzOTUtZjY5ZmQxM2Q3ZjNjIiwiY2xpZW50X2lkIjoidXNlcnNlcnZpY2UxIn0.N8HLGnDbRiN560SuE_a4IlqEZIk3yCpzpyEXH3VDb1w",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsidXNlcnNlcnZpY2UxIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2V2ZXIiXSwiYXRpIjoiODAwZjgwMmUtZTUzNS00N2RiLTkzOTUtZjY5ZmQxM2Q3ZjNjIiwiZXhwIjoxNjMxMTc2NzMyLCJ1c2VyRGV0YWlscyI6eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInBhc3N3b3JkIjpudWxsLCJyb2xlcyI6W3siaWQiOjEsIm5hbWUiOiJSRUFEIn0seyJpZCI6MSwibmFtZSI6IldSSVRFIn1dLCJhdXRob3JpdGllcyI6W3siYXV0aG9yaXR5IjoiUkVBRCJ9LHsiYXV0aG9yaXR5IjoiV1JJVEUifV0sImVuYWJsZWQiOnRydWUsImNyZWRlbnRpYWxzTm9uRXhwaXJlZCI6dHJ1ZSwiYWNjb3VudE5vbkV4cGlyZWQiOnRydWUsImFjY291bnROb25Mb2NrZWQiOnRydWV9LCJhdXRob3JpdGllcyI6WyJSRUFEIiwiV1JJVEUiXSwianRpIjoiZDMyNGQ4ZjEtMjBiMy00MTg2LTk4NzMtZjg2NDhkNjI1ZTc5IiwiY2xpZW50X2lkIjoidXNlcnNlcnZpY2UxIn0.KbqODRoK0hjrrHWeKvtZ2HNkcREPFmuv7yvOrZGzEnE",
    "expires_in": 172799,
    "scope": "sever",
    "userDetails": {
        "id": 1,
        "username": "admin",
        "password": null,
        "roles": [
            {
                "id": 1,
                "name": "READ"
            },
            {
                "id": 1,
                "name": "WRITE"
            }
        ],
        "authorities": [
            {
                "authority": "READ"
            },
            {
                "authority": "WRITE"
            }
        ],
        "enabled": true,
        "credentialsNonExpired": true,
        "accountNonExpired": true,
        "accountNonLocked": true
    },
    "jti": "800f802e-e535-47db-9395-f69fd13d7f3c"
}

 2.授权码模式

在浏览器输入地址http://localhost:8086/api/v1/oauth/authorize?response_type=code&client_id=userservice1&redirect_uri=https://baidu.com,会跳转到授权页面,授权页面利用spring mvc定义了一个页面放在resources目录下。

<!DOCTYPE html>
<html class="uk-height-1-1" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title>OAuth2 Demo</title>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/uikit/2.26.3/css/uikit.gradient.min.css" rel="stylesheet"/>
</head>

<body class="uk-height-1-1">

<div class="uk-vertical-align uk-text-center uk-height-1-1">
    <div class="uk-vertical-align-middle" style="width: 250px;">
        <h1>Login Form</h1>

        <p class="uk-text-danger" th:if="${param.error}">
            用户名或密码错误...
        </p>

        <form class="uk-panel uk-panel-box uk-form" method="post" th:action="@{/login}">
            <div class="uk-form-row">
                <input class="uk-width-1-1 uk-form-large" name="username" placeholder="Username" type="text"
                       value="admin"/>
            </div>
            <div class="uk-form-row">
                <input class="uk-width-1-1 uk-form-large" name="password" placeholder="Password" type="password"
                       value="admin"/>
            </div>
            <div class="uk-form-row">
                <button class="uk-width-1-1 uk-button uk-button-primary uk-button-large">Login</button>
            </div>
        </form>

    </div>
</div>
</body>
</html>

 跳转到登录页面,点击登录,登录成功后,跳转到百度页面,并得到code授权码

 

 拿到获取的code,生成access_token

完整的返回信息

{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsidXNlcnNlcnZpY2UxIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2V2ZXIiXSwiZXhwIjoxNjI4NzU4Mjc2LCJ1c2VyRGV0YWlscyI6eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInBhc3N3b3JkIjpudWxsLCJyb2xlcyI6W3siaWQiOjEsIm5hbWUiOiJSRUFEIn0seyJpZCI6MSwibmFtZSI6IldSSVRFIn1dLCJhdXRob3JpdGllcyI6W3siYXV0aG9yaXR5IjoiUkVBRCJ9LHsiYXV0aG9yaXR5IjoiV1JJVEUifV0sImVuYWJsZWQiOnRydWUsImNyZWRlbnRpYWxzTm9uRXhwaXJlZCI6dHJ1ZSwiYWNjb3VudE5vbkV4cGlyZWQiOnRydWUsImFjY291bnROb25Mb2NrZWQiOnRydWV9LCJhdXRob3JpdGllcyI6WyJSRUFEIiwiV1JJVEUiXSwianRpIjoiZGM5Y2FjN2UtN2M3My00YThkLWEzMzYtZTI2ZmNjYzllODE0IiwiY2xpZW50X2lkIjoidXNlcnNlcnZpY2UxIn0.sOhH-eFcldWnnOfZmDqyuPkl4Qkg8LyJnPZ0gwubBWc",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsidXNlcnNlcnZpY2UxIl0sInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsic2V2ZXIiXSwiYXRpIjoiZGM5Y2FjN2UtN2M3My00YThkLWEzMzYtZTI2ZmNjYzllODE0IiwiZXhwIjoxNjMxMTc3NDc2LCJ1c2VyRGV0YWlscyI6eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInBhc3N3b3JkIjpudWxsLCJyb2xlcyI6W3siaWQiOjEsIm5hbWUiOiJSRUFEIn0seyJpZCI6MSwibmFtZSI6IldSSVRFIn1dLCJhdXRob3JpdGllcyI6W3siYXV0aG9yaXR5IjoiUkVBRCJ9LHsiYXV0aG9yaXR5IjoiV1JJVEUifV0sImVuYWJsZWQiOnRydWUsImNyZWRlbnRpYWxzTm9uRXhwaXJlZCI6dHJ1ZSwiYWNjb3VudE5vbkV4cGlyZWQiOnRydWUsImFjY291bnROb25Mb2NrZWQiOnRydWV9LCJhdXRob3JpdGllcyI6WyJSRUFEIiwiV1JJVEUiXSwianRpIjoiNDdmYjUzMDctYzcyYy00ZjI1LWI4MjEtNTY2MGNlYWYyZGMwIiwiY2xpZW50X2lkIjoidXNlcnNlcnZpY2UxIn0.vmtnmLWsaGBxFxAZ-0nsETBUsZ3muSFKqjffAweRZTQ",
    "expires_in": 172799,
    "scope": "sever",
    "userDetails": {
        "id": 1,
        "username": "admin",
        "password": null,
        "roles": [
            {
                "id": 1,
                "name": "READ"
            },
            {
                "id": 1,
                "name": "WRITE"
            }
        ],
        "authorities": [
            {
                "authority": "READ"
            },
            {
                "authority": "WRITE"
            }
        ],
        "enabled": true,
        "credentialsNonExpired": true,
        "accountNonExpired": true,
        "accountNonLocked": true
    },
    "jti": "dc9cac7e-7c73-4a8d-a336-e26fccc9e814"
}

oauth2-resource

oauth2-resource为资源,用户在获得access_token后,携带access_token访问资源。最重要的是资源配置类。

@Configuration
//启用资源服务器
@EnableResourceServer
//启用方法注解方式来进行权限控制
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {


    /**
     * 声明了资源服务器的ID是userservice,声明了资源服务器的TokenStore是JWT
     * 注意,这里硬编码了资源id,这个资源id和client所拥有的的资源id需要匹配
     *
     * @param resources
     * @throws Exception
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId("userservice1").tokenStore(tokenStore());
    }

    /**
     * 配置TokenStore
     *
     * @return
     */
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    /**
     * 配置解析
     *
     * @return
     */
    @Bean
    protected JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        //和授权服务的key相同
        converter.setSigningKey(Constant.JWT_SECRET);
        return converter;
    }
}

security配置接口访问权限

@Configuration
@EnableWebSecurity
public class ResourceSecurityConfig extends WebSecurityConfigurerAdapter {

    //web ignore比较适合配置前端相关的静态资源,它是完全绕过spring security的所有filter的
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/resource");
    }


    /**
     * 安全请求配置,这里配置的是security的部分,这里配置全部通过,安全拦截在资源服务的配置文件中配置,
     * 要不然访问未验证的接口将重定向到登录页面,前后端分离的情况下这样并不友好,无权访问接口返回相关错误信息即可
     *
     * @param http
     * @return void
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/user/**").authenticated()
                .anyRequest().permitAll();

    }
}

ctrl接口测试类

**
 * @author honorzhang
 */

@RestController
@RequestMapping("/user")
public class ResourceUserController {

    @Autowired
    private TokenStore tokenStore;

    /***
     * 读权限或写权限可访问,返回登录用户名
     * @param authentication
     * @return
     */
    @PreAuthorize("hasAuthority('READ') or hasAuthority('WRITE')")
    @GetMapping("/name")
    public BaseResponse<String> name(OAuth2Authentication authentication) {
        return BaseResponse.success(authentication.getName());
    }

    /**
     * 读权限或写权限可访问,返回登录用户信息
     *
     * @param authentication
     * @return
     */
    @PreAuthorize("hasAuthority('READ') or hasAuthority('WRITE')")
    @GetMapping
    public BaseResponse<OAuth2Authentication> read(OAuth2Authentication authentication) {
        return BaseResponse.success(authentication);
    }

    /**
     * 只有写权限可以访问,返回访问令牌中的额外信息
     *
     * @param authentication
     * @return
     */
    @PreAuthorize("hasAuthority('WRITE') and hasAuthority('ADMIN')")
    @PostMapping
    public BaseResponse<Object> write(OAuth2Authentication authentication) {
        OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
        OAuth2AccessToken accessToken = tokenStore.readAccessToken(details.getTokenValue());
        return BaseResponse.success(accessToken.getAdditionalInformation().getOrDefault("userDetails", null));
    }
}

postman结果测试

access_token 权限包含WRITE 和 READ权限,没有ADMIN权限。

访问read接口,权限通过。

 访问write接口,没有ADMIN权限,不能访问

引用

极客时间OAuth2.0课程

写在最后

本文是根据极客时间的课程,利用spring cloud oauth2 实现的OAuth2.0权限控制。有很多是基于自己的理解,如有不正确之处,请互相指教。

代码连接

https://github.com/airhonor/authority-management-sys-2.0

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值