搭建一套自己的验证授权及资源访问服务

学习小项目系列目录

项目1:SSM + Layui + Mysql8 公司测评系统
项目2:SpringBoot+Vue+ Mysql8 大学社团管理系统
项目3:搭建自用单点OOS服务
项目4:搭建一套自己的验证授权及资源访问服务

文章目录

  • 学习小项目系列目录
    • 一、`Oauth2`
    • 二、`Spring Security`
    • 三、四种授权模式
      • 3.1 简化模式(implicit)
        • 3.1.1 地址
      • 3.2 授权码模式(authorization code)
      • 3.3 密码模式(resource owner password credentials)
      • 3.4 客户端模式(client credentials)
    • 四、搭建工程
      • 4.1 父工程
        • 4.1.2 `xml`
        • 4.1.2 项目结构
      • 4.2 授权服务器
        • 4.2.1 `xml`
        • 4.2.2 项目结构
        • 4.2.3 数据库准备
        • 4.2.4 授权服务器配置
        • 4.2.5 `Spring Security`配置
      • 4.3 资源服务器
        • 4.3.1 `xml`
        • 4.3.2 项目结构
        • 4.3.3 资源服务器配置
        • 4.3.4 配置授权地址、客户端信息等
    • 五、测试
      • 5.1 登录
      • 5.2 授权
      • 5.4 获取到`code`
      • 5.5 使用`code`获取`token`
      • 5.6 未使用`token`访问资源
      • 5.7 携带`token`访问资源
      • 5.8 刷新`token`
    • 六、参考资料
    • 七、项目地址

本文的记录了使用 Spring 提供的 Spring Security oAuth2 搭建一套验证授权及资源访问服务的详细步骤,实现企业微服务架构时能够有效的控制多个服务的统一登录、授权及资源保护工作。

一、Oauth2

oAuth 协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是 oAuth 的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此 oAuth 是安全的。

二、Spring Security

Spring Security 是一个安全框架,前身是 Acegi Security,能够为 Spring 企业应用系统提供声明式的安全访问控制。Spring Security 基于 Servlet 过滤器、IoC 和 AOP,为 Web 请求和方法调用提供身份确认和授权处理,避免了代码耦合,减少了大量重复代码工作。

三、四种授权模式

3.1 简化模式(implicit)

适用于纯静态应用,token容易泄露,不安全,不推荐使用。
搭建一套自己的验证授权及资源访问服务_第1张图片

3.1.1 地址

【授权服务地址】/oauth/authorize?client_id=【客户端ID】&response_type=token

3.2 授权码模式(authorization code)

安全系数最高.授权码模式适用于有自己的服务器的应用,它是一个一次性的临时凭证,用来换取 access_token 和 refresh_token。

【授权服务器地址】/oauth/authorize?client_id=【客户端ID】&response_type=token

搭建一套自己的验证授权及资源访问服务_第2张图片

3.3 密码模式(resource owner password credentials)

密码模式中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向 “服务商提供商” 索要授权。在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分。

【授权服务器地址】/oauth/token?client_id=web_dev&client_secret=【客户端ID】&grant_type=password&username=【用户名】&password=【密码】

搭建一套自己的验证授权及资源访问服务_第3张图片

3.4 客户端模式(client credentials)

如果信任关系再进一步,或者调用者是一个后端的模块,没有用户界面的时候,可以使用客户端模式。鉴权服务器直接对客户端进行身份验证,验证通过后,返回 token。

【授权服务器地址】/oauth/token?client_id=【客户端Id】&client_secret=【客户端密码】&grant_type=client_credentials

搭建一套自己的验证授权及资源访问服务_第4张图片

四、搭建工程

搭建一套自己的验证授权及资源访问服务_第5张图片

4.1 父工程

新建一个maven工程,添加依赖如下。

4.1.2 xml


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>

    <groupId>com.dingwengroupId>
    <artifactId>dingwen-authartifactId>
    <version>1.0-SNAPSHOTversion>
    <modules>
        
        <module>auth-authorization-servermodule>
        
        <module>auth-resource-servermodule>
    modules>
    <packaging>pompackaging>

    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.1.8.RELEASEversion>
        
        <relativePath/>
    parent>

    <properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
        <java.version>1.8java.version>
        <spring.cloud.version>Greenwich.SR1spring.cloud.version>
        <spring.cloud.starter.oauth2.version>2.1.2.RELEASEspring.cloud.starter.oauth2.version>
        <mybatis.plus.boot.starter.version>3.4.2mybatis.plus.boot.starter.version>
        <mysql.connector.java.version>8.0.15mysql.connector.java.version>
        <lombok.version>1.18.8lombok.version>
        <spring.boot.starter.test>2.1.5.RELEASEspring.boot.starter.test>
        <spring.boot.starter.web>2.1.5.RELEASEspring.boot.starter.web>
    properties>

    <dependencyManagement>
        <dependencies>
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-dependenciesartifactId>
                <version>${spring.cloud.version}version>
                <type>pomtype>
                <scope>importscope>
            dependency>

            
            <dependency>
                <groupId>com.baomidougroupId>
                <artifactId>mybatis-plus-boot-starterartifactId>
                <version>${mybatis.plus.boot.starter.version}version>
            dependency>

            
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <optional>trueoptional>
                <version>${lombok.version}version>
            dependency>

            
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
                <version>${mysql.connector.java.version}version>
            dependency>

            
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-oauth2artifactId>
                <version>${spring.cloud.starter.oauth2.version}version>
            dependency>
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
                <version>${spring.boot.starter.web}version>
            dependency>
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
                <version>${spring.boot.starter.test}version>
            dependency>
        dependencies>
    dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
            plugin>
        plugins>
    build>

project>

4.1.2 项目结构

4.2 授权服务器

4.2.1 xml


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>
    <parent>
        <groupId>com.dingwengroupId>
        <artifactId>dingwen-authartifactId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <groupId>com.dingwen.auausegroupId>
    <artifactId>auth-authorization-serverartifactId>
    <version>1.0version>
    <name>auth-authorization-servername>
    <description>授权服务器description>
    <properties>
        <java.version>1.8java.version>
    properties>
    <dependencies>

        
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
        dependency>

        
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>

        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
        dependency>

        
        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-oauth2artifactId>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
            plugin>
        plugins>
    build>

    <developers>
        <developer>
            <id>authid>
            <name>dingwenname>
            <email>[email protected]email>
        developer>
    developers>

project>

4.2.2 项目结构

搭建一套自己的验证授权及资源访问服务_第6张图片

4.2.3 数据库准备

搭建一套自己的验证授权及资源访问服务_第7张图片

sql文件在工程的doc目录下,建表语句中有详细的每张表的字段含义。

搭建一套自己的验证授权及资源访问服务_第8张图片

4.2.4 授权服务器配置

package com.dingwen.auause.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.AuthorizationServerTokenServices;
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 dingwen
 * 2021.05.12 14:37
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
     

    private static final String CLIENT_ID = "web_dev";
    private static final String SECRET = "dingwen_web";
    private static final String AUTHORIZED_GRANT_TYPES = "authorization_code";
    private static final String SCOPES = "write";
    private static final String REDIRECT_URIS = "http:///www.baidu.com";
    private final BCryptPasswordEncoder bCryptPasswordEncoder;
    private final DataSource dataSource;

    private final AuthenticationManager authenticationManager;
    private final UserDetailsService userDetailsService;

    @Autowired
    AuthorizationServerConfiguration(BCryptPasswordEncoder bCryptPasswordEncoder,
                                     DataSource dataSource,
                                     AuthenticationManager authenticationManager,
                                     UserDetailsService userDetailsService
    ) {
     
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
        this.dataSource = dataSource;
        this.authenticationManager = authenticationManager;
        this.userDetailsService = userDetailsService;
    }

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

    /*
     * jdbc 客户端服务
     * @return ClientDetailsService
     */
    @Bean
    public ClientDetailsService jdbcClientDetails() {
     // 必须是这个方法名称
        return new JdbcClientDetailsService(dataSource);
    }

    /*
     * 配置客户端
     * @param clients
     *
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
     
        // 内存模式
        /*clients //配置客户端
                .inMemory() // 试用内存设置
                .withClient(CLIENT_ID) //客户端标识
                .secret(bCryptPasswordEncoder.encode(SECRET)) //客户端安全码
                .authorizedGrantTypes(AUTHORIZED_GRANT_TYPES)//授权类型
                .scopes(SCOPES)//授权范围
                .redirectUris(REDIRECT_URIS);// 注册回调地址

         */

        // 数据库模式
        clients.withClientDetails(jdbcClientDetails());


    }

    // 设置令牌
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
     
        endpoints.tokenStore(tokenStore())
                .authenticationManager(authenticationManager)
                .tokenServices(defaultTokenServices())
                .userDetailsService(userDetailsService);
    }

    @Bean
    public AuthorizationServerTokenServices defaultTokenServices() throws Exception {
     
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setAuthenticationManager(this.authenticationManager);
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setClientDetailsService(jdbcClientDetails());

        // access token有效期2个小时
        defaultTokenServices.setAccessTokenValiditySeconds(60 * 60 * 2);
        // refresh token有效期30天
        defaultTokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 30);
        // 支持使用refresh token刷新access token
        defaultTokenServices.setSupportRefreshToken(true);
        // 允许重复使用refresh token
        defaultTokenServices.setReuseRefreshToken(true);
        return defaultTokenServices;
    }


    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
     
        security.tokenKeyAccess("permitAll()");
        security.checkTokenAccess("isAuthenticated()");
        // 允许使用客户端id和客户端secret获取token
        security.allowFormAuthenticationForClients();
    }
}




4.2.5 Spring Security配置

package com.dingwen.auause.config;

import com.dingwen.auause.service.PermissionService;
import com.dingwen.auause.service.UserService;
import com.dingwen.auause.service.impl.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;

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

/**
 * 服务器安全配置
 *
 * @author dingwen
 * 2021.05.12 14:47
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true) //全局方法拦截
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
     

    private final UserService userService;
    private final PermissionService permissionService;

    @Autowired
    WebSecurityConfiguration(UserService userService, PermissionService permissionService) {
     
        this.userService = userService;
        this.permissionService = permissionService;
    }

    /*
     * 加密方式
     * @return BCryptPasswordEncoder
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
     
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
     
        ProviderManager authenticationManager;
        authenticationManager = new ProviderManager(Collections.singletonList(authenticationProvider()));
        return authenticationManager;
    }

    /*
     * 自定义授权与认证
     * @return UserDetailsService
     */
    @Bean
    public UserDetailsService userDetailsService() {
     
        return new UserDetailsServiceImpl(userService, permissionService);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
     
        /*auth.inMemoryAuthentication() //在内存中创建用户并进行加密
                .withUser("dingwen")
                .password(bCryptPasswordEncoder()
                        .encode("123456"))
                .roles("USER")
                .and()
                .withUser("admin")
                .password(bCryptPasswordEncoder()
                        .encode("123456"))
                .roles("ADMIN");*/

//        auth.authenticationProvider(authenticationProvider());
        auth.userDetailsService(userDetailsService()); // 使用自定义用户授权
    }


    @Override
    public void init(WebSecurity web) throws Exception {
     
        super.init(web);
        // 将 check_token 暴露出去,否则资源服务器访问时报 403 错误
        web.ignoring().antMatchers("/oauth/check_token");
    }

    /**
     * 认证
     */
    @Bean
    public AuthenticationProvider authenticationProvider() {
     
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        //对默认的UserDetailsService进行覆盖
        authenticationProvider.setUserDetailsService(userDetailsService());
        return authenticationProvider;
    }


}

4.3 资源服务器

4.3.1 xml


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>
    <parent>
        <groupId>com.dingwengroupId>
        <artifactId>dingwen-authartifactId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <groupId>com.dingwen.auresegroupId>
    <artifactId>auth-resource-serverartifactId>
    <version>1.0version>
    <name>auth-resource-servername>
    <description>资源服务器description>
    <properties>
        <java.version>1.8java.version>
    properties>
    <dependencies>

        
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
        dependency>

        
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>

        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
        dependency>

        
        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-oauth2artifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
            plugin>
        plugins>
    build>

    <developers>
        <developer>
            <id>authid>
            <name>dingwenname>
            <email>[email protected]email>
        developer>
    developers>

project>

4.3.2 项目结构

搭建一套自己的验证授权及资源访问服务_第9张图片

4.3.3 资源服务器配置

package com.dingwen.aurese.config;

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.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;

/**
 * 资源服务器配置
 *
 * @author dingwen
 * 2021.05.13 14:37
 */
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
     

    @Override
    public void configure(HttpSecurity http) throws Exception {
     
        http
            .exceptionHandling() //异常处理器
            .and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 禁用session
            .and()
            .authorizeRequests()
            .antMatchers("/before").hasAnyAuthority("be"); // 用于测试访问/before资源的人需要有be权限
    }
}

4.3.4 配置授权地址、客户端信息等

security:
  oauth2:
    client:
      client-id: web-dev
      client-secret: 123456
      access-token-uri: http://localhost:9001/oauth/token # 请求令牌的地址
      user-authorization-uri: http://localhost:9001/oauth/authorize # 授权地址
    resource:
      token-info-uri: http://localhost:9001/oauth/check_token # 检查 token 是否有效

五、测试

5.1 登录

【授权服务器地址】/oauth/authorize?client_id=web_dev&response_type=code
搭建一套自己的验证授权及资源访问服务_第10张图片
搭建一套自己的验证授权及资源访问服务_第11张图片

5.2 授权

搭建一套自己的验证授权及资源访问服务_第12张图片

5.4 获取到code

搭建一套自己的验证授权及资源访问服务_第13张图片

5.5 使用code获取token

搭建一套自己的验证授权及资源访问服务_第14张图片

5.6 未使用token访问资源

搭建一套自己的验证授权及资源访问服务_第15张图片

5.7 携带token访问资源

前提是当前登录的这个用户有这个资源的权限,详细见Spring Security的配置
搭建一套自己的验证授权及资源访问服务_第16张图片

5.8 刷新token

搭建一套自己的验证授权及资源访问服务_第17张图片
搭建一套自己的验证授权及资源访问服务_第18张图片

六、参考资料

https://www.funtl.com/zh/spring-security-oauth2

七、项目地址

https://gitee.com/dingwen-gitee/dingwen-auth

你可能感兴趣的:(study,project,oauth2,java)