• Spring Security OAuth2:认证服务器实现


    授权码模式

    创建父工程

    cloud-oauth2-parent

    pom文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <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.0</modelVersion>
    
        <groupId>com.wj</groupId>
        <artifactId>cloud-oauth2-parent</artifactId>
        <version>1.0-SNAPSHOT</version>
        <modules>
            <module>cloud-oauth2-base</module>
            <module>cloud-oauth2-auth-server</module>
        </modules>
        <packaging>pom</packaging>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.2.2.RELEASE</version>
            <relativePath/>
        </parent>
    
        <properties>
            <spring-cloud.version>Hoxton.SR1</spring-cloud.version>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <maven.compiler.source>1.8</maven.compiler.source>
            <maven.compiler.target>1.8</maven.compiler.target>
            <mybatis-plus.version>3.2.0</mybatis-plus.version>
            <druid.version>1.1.12</druid.version>
            <kaptcha.version>2.3.2</kaptcha.version>
            <fastjson.version>1.2.8</fastjson.version>
            <commons-lang.version>2.6</commons-lang.version>
            <commons-collections.version>3.2.2</commons-collections.version>
            <commons-io.version>2.6</commons-io.version>
            <!-- 定义版本号, 子模块直接引用-->
            <oauth-security.version>1.0-SNAPSHOT</oauth-security.version>
        </properties>
    
    
        <!--依赖声明-->
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>${spring-cloud.version}</version>
                    <type>pom</type>
                    <!--maven不支持多继承,使用import来依赖管理配置-->
                    <scope>import</scope>
                </dependency>
                <!--mybatis-plus启动器-->
                <dependency>
                    <groupId>com.baomidou</groupId>
                    <artifactId>mybatis-plus-boot-starter</artifactId>
                    <version>${mybatis-plus.version}</version>
                </dependency>
                <!--druid连接池-->
                <dependency>
                    <groupId>com.alibaba</groupId>
                    <artifactId>druid</artifactId>
                    <version>${druid.version}</version>
                </dependency>
                <!-- kaptcha 用于图形验证码 -->
                <dependency>
                    <groupId>com.github.penggle</groupId>
                    <artifactId>kaptcha</artifactId>
                    <version>${kaptcha.version}</version>
                </dependency>
                <!-- 工具类依赖 -->
                <dependency>
                    <groupId>com.alibaba</groupId>
                    <artifactId>fastjson</artifactId>
                    <version>${fastjson.version}</version>
                </dependency>
    
                <dependency>
                    <groupId>commons-lang</groupId>
                    <artifactId>commons-lang</artifactId>
                    <version>${commons-lang.version}</version>
                </dependency>
                <dependency>
                    <groupId>commons-collections</groupId>
                    <artifactId>commons-collections</artifactId>
                    <version>${commons-collections.version}</version>
                </dependency>
                <dependency>
                    <groupId>commons-io</groupId>
                    <artifactId>commons-io</artifactId>
                    <version>${commons-io.version}</version>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.7.0</version>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
                <!--springboot 打包插件-->
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>2.2.4.RELEASE</version>
                </plugin>
            </plugins>
        </build>
    </project>
    

    创建子工程

    cloud-oauth2-base

    pom依赖

    <?xml version="1.0" encoding="UTF-8"?>
    <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">
        <parent>
            <artifactId>cloud-oauth2-parent</artifactId>
            <groupId>com.wj</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>cloud-oauth2-base</artifactId>
    
        <dependencies>
            <!--类中setter/getter,使用注解-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
            <!-- 工具类依赖 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
            </dependency>
            <dependency>
                <groupId>commons-lang</groupId>
                <artifactId>commons-lang</artifactId>
            </dependency>
            <dependency>
                <groupId>commons-collections</groupId>
                <artifactId>commons-collections</artifactId>
            </dependency>
            <dependency>
                <groupId>commons-io</groupId>
                <artifactId>commons-io</artifactId>
            </dependency>
        </dependencies>
    </project>
    

    创建通用返回类:com.wj.base.result.R

    @Data
    public class R implements Serializable {
    
        // 响应业务状态
        private Integer code;
    
        // 响应消息
        private String message;
    
        // 响应中的数据
        private Object data;
    
        public R() {
        }
        public R(Object data) {
            this.code = 200;
            this.message = "OK";
            this.data = data;
        }
        public R(String message, Object data) {
            this.code = 200;
            this.message = message;
            this.data = data;
        }
    
        public R(Integer code, String message, Object data) {
            this.code = code;
            this.message = message;
            this.data = data;
        }
    
        public static R ok() {
            return new R(null);
        }
        public static R ok(String message) {
            return new R(message, null);
        }
        public static R ok(Object data) {
            return new R(data);
        }
        public static R ok(String message, Object data) {
            return new R(message, data);
        }
    
        public static R build(Integer code, String message) {
            return new R(code, message, null);
        }
    
        public static R build(Integer code, String message, Object data) {
            return new R(code, message, data);
        }
    
        public String toJsonString() {
            return JSON.toJSONString(this);
        }
    
        /**
         * JSON字符串转成 R 对象
         */
        public static R format(String json) {
            try {
                return JSON.parseObject(json, R.class);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    
    

    cloud-oauth2-auth-server

    基本配置

    pom依赖:

    <?xml version="1.0" encoding="UTF-8"?>
    <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">
        <parent>
            <artifactId>cloud-oauth2-parent</artifactId>
            <groupId>com.wj</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>cloud-oauth2-auth-server</artifactId>
    
        <dependencies>
            <dependency>
                <artifactId>cloud-oauth2-base</artifactId>
                <groupId>com.wj</groupId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
            <!--spring mvc相关的-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!-- Spring Security、OAuth2 和JWT等 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-oauth2</artifactId>
            </dependency>
    
            <!-- 注册到 Eureka
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
            -->
    
            <!-- redis-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
    
            <!--mybatis-plus启动器-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
    
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
            </dependency>
    
        </dependencies>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>2.2.4.RELEASE</version>
                    <configuration>
                        <mainClass>com.wj.oauth2.AuthServerApplication</mainClass>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>
    

    application.yml

    server:
      port: 8090
      servlet:
        context-path: /auth
    spring:
      datasource:
        username: root
        password: 1234
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/study-security?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8
    

    springboot主启动类com.wj.oauth2.AuthServerApplication:

    @SpringBootApplication
    public class AuthServerApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(AuthServerApplication.class, args);
        }
    
    }
    
    配置类

    统一管理Bean配置类: SpringSecurityBean

    @Configuration
    public class SpringSecurityBean {
        @Bean  //引入PasswordEncoder
        public PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
    }
    

    SpringSecurity配置类:

    @EnableWebSecurity
    public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            // 内存方式存储用户信息,这里为了方便就不从数据库中查询了
            auth.inMemoryAuthentication().withUser("admin")
                    .password(passwordEncoder.encode("1234"))
                    .authorities("product");
        }
    }
    

    认证服务器配置类:AuthorizationServerConfig

    @Configuration
    @EnableAuthorizationServer//开启认证服务器功能
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        /**  配置被允许访问此认证服务器的客户端详情信息
         * 方式1:内存方式管理
         * 方式2:数据库管理
         */
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            // 使用内存方式
            clients.inMemory()
                    // 客户端id
                    .withClient("wj-pc")
            // 客户端密码,要加密,不然一直要求登录
            .secret(passwordEncoder.encode("wj-secret"))
            // 资源id, 如商品资源
            .resourceIds("product-server")
            // 授权类型, 可同时支持多种授权类型
            .authorizedGrantTypes("authorization_code", "password", "implicit","client_credentials","refresh_token")
            // 授权范围标识,哪部分资源可访问(all是标识,不是代表所有)
            .scopes("all")
            // false 跳转到授权页面手动点击授权,true 不用手动授权,直接响应授权码,
            .autoApprove(false)
            .redirectUris("http://www.baidu.com/");// 客户端回调地址
        }
    }
    

    配置说明:

    withClient:允许访问此认证服务器的客户端id , 如:PC、APP、小程序各不同的的客户端id。

    secret:客户端密码,要加密存储,不然获取不到令牌一直要求登录,, 而且一定不能被泄露。

    authorizedGrantTypes: 授权类型, 可同时支持多种授权类型:可配置:"authorization_code", "password", "implicit","client_credentials","refresh_token"

    scopes:授权范围标识,如指定微服务名称,则只能访问指定的微服务。

    autoApprove:false 跳转到授权页面手动点击授权,true 不用手动授权,直接响应授权码

    redirectUris 当获取授权码后,认证服务器会重定向到这个URI,并且带着一个授权码code响应回来

    令牌访问端点

    Spring Security 对 OAuth2 默认提供了可直接访问端点,即URL:

    /oauth/authorize:申请授权码 code, 涉及的类AuthorizationEndpoint

    /oauth/token:获取令牌 token, 涉及的类TokenEndpoint/oauth/check_token:用于资源服务器请求端点来检查令牌是否有效, 涉及的类CheckTokenEndpoint

    /oauth/confirm_access:用户确认授权提交, 涉及的类WhitelabelApprovalEndpoint

    /oauth/error:授权服务错误信息, 涉及的类WhitelabelErrorEndpoint

    /oauth/token_key:提供公有密匙的端点,使用 JWT 令牌时会使用 , 涉及的类TokenKeyEndpoint

    测试

    发送请求获取授权码code

    访问:localhost:8090/auth/oauth/authorize?client_id=wj-pc&response_type=code

    这里的client_id是在AuthorizationServerConfig中配置的。

    输入账号密码进行登陆,账号和密码:admin/1234 ,是在SpringSecurityConfig中配置的

    image-20210308172218218

    登陆成功后,选择Approve,点击Authorize,这里跳转到www.baidu.com ,并且后面携带了code,这里的code就是授权码,后面我们就可以通过授权码来获取令牌(access_token)

    image-20210308172256770

    通过授权码获取令牌

    使用postman测试:http://localhost:8090/auth/oauth/token

    image-20210308172928031

    这里的username和password是在AuthorizationServerConfig中配置的

    设置为post请求,并设置请求体

    image-20210308175033878

    这里的grant_type是authorization_code,code是上一步获取的code

    发送请求后,获得了access_token

    image-20210308175522928

    注意,code只能获取一次access_token,获取后就会失效,第二次获取就会失败

    image-20210308175604772

    密码授权模式

    密码模式(Resource Owner Password Credentials Grant)中,用户向客户端提供自己在服务提供商(认证服务器)上的用户名和密码,然后客户端通过用户提供的用户名和密码向服务提供商(认证服务器)获取令牌。

    如果用户名和密码遗漏,服务提供商(认证服务器)无法判断客户端提交的用户和密码是否盗取来的,那意味着令牌就可随时获取,数据被丢失。

    所以密码授权模式适用于产品都是企业内部的,用户名密码共享不要紧。如果是第三方这种不太适合。也适用手机APP提交用户名密码。

    配置密码模式

    修改SpringSecurityConfig配置类:

    @EnableWebSecurity
    public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            // 内存方式存储用户信息
            auth.inMemoryAuthentication().withUser("admin")
                    .password(passwordEncoder.encode("1234"))
                    .authorities("product");
        }
    
        /**
         * password密码模式需要使用此认证管理器
         */
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    }
    

    修改AuthorizationServerConfig类:

    @Configuration
    @EnableAuthorizationServer//开启认证服务器功能
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Autowired
        private AuthenticationManager authenticationManager;
    
        /**  配置被允许访问此认证服务器的客户端详情信息
         * 方式1:内存方式管理
         * 方式2:数据库管理
         * localhost:8090/auth/oauth/authorize?client_id=wj-pc&response_type=code
         */
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            // 使用内存方式
            clients.inMemory()
                    // 客户端id
                    .withClient("wj-pc")
            // 客户端密码,要加密,不然一直要求登录, 获取不到令牌, 而且一定不能被泄露
            .secret(passwordEncoder.encode("wj-secret"))
            // 资源id, 如商品资源
            .resourceIds("product-server")
            // 授权类型, 可同时支持多种授权类型
            .authorizedGrantTypes("authorization_code", "password", "implicit","client_credentials","refresh_token")
            // 授权范围标识,哪部分资源可访问(all是标识,不是代表所有)
            .scopes("all")
            // false 跳转到授权页面手动点击授权,true 不用手动授权,直接响应授权码,
            .autoApprove(false)
            .redirectUris("http://www.baidu.com/");// 客户端回调地址
        }
    
        /**
         * 重写父类的方法
         */
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            //密码模式需要设置此认证管理器
            endpoints.authenticationManager(authenticationManager);
        }
    }
    

    测试使用

    使用postman测试:

    访问:http://localhost:8090/auth/oauth/token

    image-20210309092223859

    填上请求参数:

    image-20210309092340231

    发送请求即可获取到access_token:

    image-20210309092419351

    简化模式

    不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,不需要先获取授权码。直接可以一次请求就可得到令牌,在redirect_uri指定的回调地址中传递令牌( access_token )。该模式适合直接运行在浏览器上的应用,不用后端支持(例如 Javascript 应用)

    注意:只需要客户端id,客户端密码都不需要。

    浏览器直接访问:localhost:8090/auth/oauth/authorize?client_id=wj-pc&response_type=token

    先登录:

    image-20210309092818515

    登陆成功并授权后,直接跳转到指定页面,并在uri中返回了access_token

    image-20210309092920599

    注意

    • 简化模式不允许按照 OAuth2 规范发布刷新令牌(refresh token)。这种行为是有必要的,它要求在使用运行在浏览器中的程序时,用户必须在场,这样可以在任何需要的时候,给第三方应用授权。

    • 当使用简化模式时,第三方应用始终需要通过重定向URI来注册,这样能确保不会将token传给不需要验证的客户端。如果不这样做,一些心怀不轨的用户可能先注册一个应用,然后试图让其他的应用来顶替,接收这个 token,这样可能导致灾难性的结果

    客户端授权模式

    客户端模式(Client Credentials Grant)指客户端以自己的名义,而不是以用户的名义,向服务提供商(认证服务器)进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求服务提供商(认证服务器)提供服务,其实不存在授权问题。

    • 客户端向认证服务器进行身份认证,并要求一个访问令牌。

    • 认证服务器确认无误后,向客户端提供访问令牌

    测试

    post测试:http://localhost:8090/auth/oauth/token

    image-20210309093435745

    grant_type等于client_credentials

    image-20210309093543715

    发送请求返回了access_token,注意响应结果没有刷新令牌的

    image-20210309093620031

  • 相关阅读:
    三、oneinstack
    二、两条Linux删除数据跑路命令
    一、定时任务
    三、Docker网络
    C#数据结构
    四、字段大全
    【2019-06-14】同事的低迷
    【2019-06-13】同事的进步
    【2019-06-12】看书的好地方
    【2019-06-11】笔耕不辍
  • 原文地址:https://www.cnblogs.com/wwjj4811/p/14503898.html
Copyright © 2020-2023  润新知