SpringSecurity-OAuth2 授权

SpringSecurity-OAuth2 授权

授权模式复杂程度由大至小:授权码模式 > 隐式授权模式 > 密码模式 > 客户端模式

(1)客户端模式

其中客户端模式的流程是:客户端使用授权服器给的标识与secret访问资源服务器获取token

没有用户与授权服务器交互,完全以机器形式获取授权与使用授权,客户端机器 <-> 授权服务器,这种方式主要用于第一方应用中

(2)密码模式

其中密码模式的流程是:让用户填写表单提交到授权服务器,表单中包含用户的用户名、密码、客户端的id和密钥的加密串,授权服务器先解析并校验客户端信息,然后校验用户信息,完全通过返回access_token,否则默认都是401 http状态码,提示未授权无法访问

(3) 隐式授权模式

适用场景有以下几个条件:

  • 用户参与:使用隐式授权需要与用户交互,用户对授权服务器进行登录与授权
  • 单页应用:SPA前端,没有后端或者后端属于授权方
  • 客户端密码:访问授权时,不需要带第三方应用secret,前提是资源服务校验token使用的client信息与客户端(第三方应用)不同,且配置了secret
  • 前端:必须要有前端,否则无法使用授权功能
  • 客户端后端:Options,仅当应用前后端不分离MVC场景
  • 资源所属方:授权方

(4)授权码模式

授权码模式要求:用户登录并对第三方应用(客户端)进行授权,出示授权码交给客户端,客户端凭授权码换取access_token(访问凭证)

此模式要求授权服务器与用户直接交互,在此过程中,第三方应用是无法获取到用户输入的密码等信息的,这个模式也是OAuth 2.0中最安全的一个

http://localhost:8080/oauth/authorize?client_id=client-a&client_secret=client-a-secret&response_type=code

登录用户/密码: hellxz/xyz ,选择Approve表示接受授权,Deny反之

http://localhost:9001/callback?code=4mak0O

http://localhost:8080/oauth/token

BasicAuth:这里填的是客户端配置的client_id和client_secret的值,相当于curl --user client_id:client_secret,配置后会在Header中添加Authorization:Basic Y2xpZW50LWE6Y2xpZW50LWEtc2VjcmV0Basic空格 后的是client_id:client_secret具体值被Base64后得到的值

请求参数列表:

  • code=授权码
  • grant_type=authorization_code
  • redirect_uri=回调url ,要与配置处和获取授权码处相同
  • scope=作用域

http://localhost:8080/oauth/token?code=5RZ2mJ&grant_type=authorization_code&redirect_uri=http://localhost:9001/callback&scope=read_user_info

http://localhost:8081/user/hellxz001

WebSecurityConfigurerAdapter

@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("hellxz")
                .password(passwordEncoder().encode("xyz"))
                .authorities(new ArrayList<>(0));
    }




WebSecurityConfigurerAdapter

AuthorizationServerConfigurerAdapter

AuthorizationServerConfigurerAdapter
//配置客户端
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //@formatter:off
        clients.inMemory()
                .withClient("client-a")
                  .secret(passwordEncoder.encode("client-a-secret"))
                  .authorizedGrantTypes("password")
                  .scopes("read_scope");
        //@formatter:on
    }


@Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // @formatter: off
        clients.inMemory()
                .withClient("client-a") //client端唯一标识
                    .authorizedGrantTypes("implicit") //授权模式标识
                    .accessTokenValiditySeconds(120) //访问令牌的有效期,这里设置120s
                    .scopes("read_user_info") //作用域
                    .resourceIds("resource1") //资源id
                    .redirectUris("http://localhost:9001/callback") //回调地址
                    .and()
                .withClient("resource-server") //资源服务器校验token时用的客户端信息,仅需要client_id与密码
                    .secret(passwordEncoder.encode("test"));
        // @formatter: on
    }



@Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // @formatter: off
        clients.inMemory()
                .withClient("client-a") //client端唯一标识
                    .secret(passwordEncoder.encode("client-a-secret")) //client-a的密码,这里的密码应该是加密后的
                    .authorizedGrantTypes("client_credentials","password", "refresh_token") //授权模式标识,开启刷新token功能
                    .scopes("read_user_info", "service", "users") //作用域
                    .resourceIds("resource1") //资源id,如不需限制资源id,注释此处即可
                    .redirectUris("http://localhost:9001/callback"); //回调地址

        // @formatter: on
    }


资源服务器只做token的校验与给予资源

ResourceServerConfigurerAdapter

ResourceServerConfigurerAdapter 
@Bean
    public RemoteTokenServices remoteTokenServices() {
        final RemoteTokenServices tokenServices = new RemoteTokenServices();
        tokenServices.setClientId("client-a");
        tokenServices.setClientSecret("client-a-secret");
        tokenServices.setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token");
        return tokenServices;
    }

@Primary
    @Bean
    public RemoteTokenServices remoteTokenServices() {
        final RemoteTokenServices tokenServices = new RemoteTokenServices();
        tokenServices.setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token");
        //这里的clientId和secret对应资源服务器信息,授权服务器处需要配置
        tokenServices.setClientId("resource-server");
        tokenServices.setClientSecret("test");
        return tokenServices;
    }


本文仅说明密码模式的精简化配置,某些部分如资源服务再访问授权服务去校验token这部分生产环境可能会换成Jwt、Redis等tokenStore实现,授权服务器中的用户信息与客户端信息生产环境应从数据库中读取,对应Spring Security的UserDetailsService实现类或用户信息的Provider等

浏览器访问地址:http://localhost:8080/oauth/authorize?client_id=client-a&redirect_uri=http://localhost:9001/callback&response_type=token&scope=read_user_info

请求参数列表:

  • client_id=客户端id
  • redirect_uri=回调url 一定要与授权服务器配置保持一致,否则得不到授权码
  • response_type=token 简化模式必须是token
  • scope=作用域 与授权服务器配置保持一致
  • state=自定义串(可选)

OAuth2定义了四种授权模式(授权流程)来对资源的访问进行控制

授权码模式(Authorization Code Grant)
隐式授权模式(Implicit Grant)
用户名密码模式(Resource Owner Password Credentials Grant)
客户端模式(Client Credentials Grant)
无论哪个模式(流程)都拥有三个必要角色:客户端、授权服务器、资源服务器,有的还有用户(资源拥有者)

keytool -genkeypair -alias hellxz-jwt -validity 3650 -keyalg RSA -keypass hellxzTest -keystore hellxz-jwt.jks -storepass hellxzTest
➜ keytool -list -rfc --keystore hellxz-jwt.jks | openssl x509 -inform pem -pubkey
输入密钥库口令:  hellxzTest


netstat -ano | findstr [端口号]
netstat -ano | findstr 19003
netstat -ano | findstr 9003
netstat -ano | findstr 9007

另外使用JWT应设置尽量短的过期时间,因为JWT的token无法手动revoke,只能等待其到达过期时间失效

 /**
     * 客户端信息配置,可配置多个客户端,这里可以使用配置文件进行代替
     *
     * @param clients 客户端设置
     * @throws Exception 异常
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("client-a")
                .secret(passwordEncoder.encode("client-a-secret"))
                .redirectUris("http://localhost:9001/callback")
                //支持 授权码、密码两种授权模式,支持刷新token功能
                .authorizedGrantTypes("authorization_code", "password", "refresh_token");
    }
/**
     * 配置端点
     *
     * @param endpoints 端点
     * @throws Exception 异常
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //配置认证管理器
        endpoints.authenticationManager(authenticationManager)
                //配置用户服务
                .userDetailsService(userDetailsService)
                //配置token存储的服务与位置
                .tokenServices(tokenService())
                .tokenStore(tokenStore());
    }

    @Bean
    public TokenStore tokenStore() {
        //使用redis存储token
        RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
        //设置redis token存储中的前缀
        redisTokenStore.setPrefix("auth-token:");
        return redisTokenStore;
    }

    @Bean
    public DefaultTokenServices tokenService() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        //配置token存储
        tokenServices.setTokenStore(tokenStore());
        //开启支持refresh_token,此处如果之前没有配置,启动服务后再配置重启服务,可能会导致不返回token的问题,解决方式:清除redis对应token存储
        tokenServices.setSupportRefreshToken(true);
        //复用refresh_token
        tokenServices.setReuseRefreshToken(true);
        //token有效期,设置12小时
        tokenServices.setAccessTokenValiditySeconds(12 * 60 * 60);
        //refresh_token有效期,设置一周
        tokenServices.setRefreshTokenValiditySeconds(7 * 24 * 60 * 60);
        return tokenServices;
    }

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    // 配置客户端
    clients
            // 使用内存设置
            .inMemory()
            // client_id
            .withClient("client")
            // client_secret
            .secret(passwordEncoder.encode("secret"))
            // 授权类型: 授权码、刷新令牌、密码、客户端、简化模式、短信验证码 "refresh_token"
            .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "sms_code")
            // 授权范围,也可根据这个范围标识,进行鉴权
            .scopes("admin:org","write:org","read:org")
            .accessTokenValiditySeconds(300)
            .refreshTokenValiditySeconds(3000)
            // 授权码模式 授权页面是否自动授权
            //.autoApprove(false)
            // 拥有的权限
            .authorities("add:user")
            // 允许访问的资源服务 ID
            //.resourceIds("oauth2-resource-server001-demo")
            // 注册回调地址
            .redirectUris("http://localhost:20000/code");
}

Spring boot——Actuator 详解
https://blog.csdn.net/weixin_45985053/article/details/125872921
https://zhuanlan.zhihu.com/p/343095559

SpringBoot整合可视化监控工具——SpringBoot Admin
https://zhuanlan.zhihu.com/p/539661461
https://cloud.tencent.com/developer/article/2145410
https://blog.csdn.net/jiangjun_dao519/article/details/125242434
https://blog.csdn.net/goodjava2007/article/details/126395140

keytool

keytool为java原生自带,安装java后不需要再进行安装,作为密钥和证书管理工具,方便用户能够管理自己的公钥/私钥及证书,用于认证服务。
https://blog.csdn.net/Guesshat/article/details/123024693

keytool -genkeypair -alias hellxz-jwt -validity 3650 -keyalg RSA -keypass hellxzTest -keystore hellxz-jwt.jks -storepass hellxzTest
➜ keytool -list -rfc --keystore hellxz-jwt.jks | openssl x509 -inform pem -pubkey
输入密钥库口令:  hellxzTest


keytool -genkeypair -alias hsAdmin-jwt-dev -validity 3650 -keyalg RSA -keypass adminDev -keystore hsAdmin-jwt-dev.jks -storepass adminDev
➜ keytool -list -rfc --keystore hsAdmin-jwt-dev.jks | openssl x509 -inform pem -pubkey
输入密钥库口令:  adminDev

keytool -list -v -keystore hsAdmin-jwt-dev.jks -storepass adminDev

参考

https://www.cnblogs.com/hellxz/p/oauth2_process.html
JWT简要说明
使用Redis作为Spring Security OAuth2的token存储
maven的pom文件中的作用
https://blog.csdn.net/Guesshat/article/details/123024693
JKS和PKCS#12

posted @ 2023-09-16 11:07  三里清风18  阅读(646)  评论(0)    收藏  举报