24.SpringSecurity-实现标准的OAuth服务提供商

前言

  1. 实现一个标准的OAuth2协议中Provider角色的主要是实现2个东西:
    a. 认证服务器
    b. 受权服务器

内容

1.基本项目启动

讲解基于先后端分离和App模式的token受权开发,因此咱们代码主要写在spring-security-app里面。 以前的spring-security-demo项目一直是依赖咱们的spring-security-web项目,咱们如今改为spring-security-demo项目依赖咱们的:spring-security-app项目:html

<!-- <dependency>
    <artifactId>spring-security-web</artifactId>
    <groupId>com.yxm.security</groupId>
    <version>${project.version}</version>
</dependency>-->

<dependency>
    <groupId>com.yxm.security</groupId>
    <artifactId>spring-security-app</artifactId>
    <version>${project.version}</version>
</dependency>

由于spring-security-app是一个空的项目,咱们如今启动下,看demo项目是否能够正常启动
启动项目时候报错:web

***************************
APPLICATION FAILED TO START
***************************

Description:
Field authenticationFailureHandler in com.yxm.security.core.validate.code.ValidateCodeFilter 
required a bean of type 'org.springframework.security.web.authentication.AuthenticationFailureHandler' that could not be found.

Action:
Consider defining a bean of type 'org.springframework.security.web.authentication.AuthenticationFailureHandler' in your configuration.

报错缘由是咱们的的spring-security-core须要一个类:
image.pngspring

以前的认证成功和失败是在spring-security-web里面进行的,因为咱们web和app对认证失败后的处理是不同的,因此咱们在app里面咱们单独定义本身的认证失败,认证成功处理器。 数据库

而后咱们把SimpleResponse移动到spring-security-core里面的support包中。
image.png后端

***************************
APPLICATION FAILED TO START
***************************

Description:

Field passwordEncoder in com.yxm.security.service.MyUserDetailsService required a bean of type 'org.springframework.security.crypto.password.PasswordEncoder' that could not be found.

Action:

Consider defining a bean of type 'org.springframework.security.crypto.password.PasswordEncoder' in your configuration.

咱们以前是在spring-security-web里面配置了浏览器

@Bean
    public PasswordEncoder  passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

如今咱们在spring-security-web和spring-security-app都须要使用PasswordEncoder,因此咱们将其配置在spring-security-core里面的:SecurityCoreConfig服务器

@Configuration//配置类
@EnableConfigurationProperties(SecurityProperties.class)//让配置类生效
public class SecurityCoreConfig {
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

如今咱们服务正常启动。微信

2.认证服务器

image.png

  1. 以前咱们说过,Spring Security OAuth把咱们认证服务器里面的4种受权模式,Token的生成存储都已经实现了,咱们只须要实现黄色部分,咱们只须要写一个注解:@EnableAuthorizationServer咱们就实现了认证服务器。由于咱们demo项目依赖了app项目,app项目实现了认证服务器,因此demo也是认证服务器,那么就能提供4中受权模式。
@Configuration
@EnableAuthorizationServer
public class MyAuthorizationServerConfig {
}

3. 4种受权模式

3.1 受权码模式

image.png

咱们知道以前受权码模式对于服务提供商而言:他要提供2个服务地址,一个是让用户跳过来去点击“受权”的服务地址;另一个就是:点完受权以后,带着受权码过来换access_token的地址。 app

咱们启动应用,发现console控制台打印出了不少地址:
image.png前后端分离

/oauth/authorize(第一个地址):让用户跳过来去确认受权的路径。
/oauth/token(第二个地址):让用户去拿着受权码过来换access_token的地址路径。

咱们在浏览器访问下第一个地址:
咱们进入oauth2的官网:https://tools.ietf.org/html/rfc6749
去查找OAuth2的获取受权码的参数说明:

image.png
image.png

咱们如今主要是组装这几个参数:
response_type:值:code
client_id:值从咱们服务启动时候会打印出client_id:e0fb9540-74d1-4477-a8b7-b7bc89986176
image.png

redirect_uri:http://example.com
scope:all
state:可选

则url地址为:http://localhost:8088/oauth/authorize?response_type=code&client_id=e0fb9540-74d1-4477-a8b7-b7bc89986176&redirect_uri=http://example.com&scope=all

访问以后,弹出一个basic的信息,须要咱们输入用户名和密码:
image.png

咱们如今扮演的是服务提供商:qq,微信这种角色,那么这个地址帐号密码其实是咱们服务提供商提供给第三方应用的,让他去引导用户受权的,做为我来讲,我须要知道3件事:

  1. 哪个应用在请求这个受权? 百度或者阿里----->经过client_id(每一个应用注册的时候分配)
  2. 请求个人哪一个用户进行受权?---->咱们上面须要输入的用户名和密码来确认系统中哪一个用户在受权。
  3. 给你什么受权?----->请求什么样的权限,是经过参数scope带古来的。scope是服务端提供的。

总结就是:哪一个第三方应用须要哪一个用户进行什么权限。
用户名/密码是使用:UserDetailsService去校验
image.png

因此咱们输入的时候,用户名能够随便输入,面必须是123456便可。
image.png
而后咱们进入403页面:
image.png

由于在默认条件下:认证服务器请求必定要有一个ROLE_USER的角色才能访问
因此咱们在后面加一个ROLE_USER配置。

private SocialUserDetails buildUser(String userId){
    // 根据用户名查找用户信息
    //根据查找到的用户信息判断用户是否被冻结
    String password = passwordEncoder.encode("123456");
    logger.info("数据库密码是:"+password);
    return new SocialUser(userId, password,
            true, true, true, true,
            AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_USER"));
}

在重启服务以前咱们作一个一个配置,以前咱们服务提供商提供的client_id是在应用启动时候,写死的。每次都要变,咱们本身来生成如下。

//咱们配置服务提供商给哪些第三方(client)应用去提供受权服务。咱们配置给yxm提供服务
security:
    oauth2:
      client:
        clientId: yxm
        secret: yxmsecret

重启服务此时咱们访问的url为:

image.png

http://localhost:8088/oauth/authorize?response_type=code&client_id=yxm&redirect_uri=http://example.com&scope=all

咱们就能够看到相似qq受权登陆,微信受权扫码的页面:
image.png

赞成或者拒绝受权是咱们服务提供方作的:咱们点击赞成:
而后带着咱们的受权码跳转到redirect_uri指定的页面-便是咱们第三方的页面。

第三方应用拿到受权码以后,应该拿着这个受权码去发一个请求换取:access_token;也就是调用咱们/oauth/token(第二个地址)
而后咱们使用浏览器请求工具发送获取access_token请求
image.png

咱们须要携带请求头Header:
Authorization:
image.png

request_body中内容以下:
grant_type:authorization_code
client_id:yxm
code:NBP3tW

image.png
redirect_uri:http://example.com
scope:all
而后咱们发送请求:这个时候返回以下:
image.png

3.2 用户名/密码模式

密码模式至关因而服务提供方微信/qq把用户名/密码告诉了第三方,第三方拿着这个用户名/密码去获取access_token。服务提供方无法判断此用户名/密码来源。在微信这种服务提供方和第三方不是同一厂家的话,这种方式是不可行的,可是在咱们经常使用业务系统开发中用于咱们服务提供方和第三方App都是同一厂家是一伙的,因此可行。

与受权码的url和headers一致;只有BODY不同:
image.png

从地址:https://tools.ietf.org/html/r...
image.png

POST /token HTTP/1.1 Host:server.example.com Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW Content-Type: application/x-www-form-urlencoded

http://127.0.0.1:8088/oauth/token?grant_type=password&username=johndoe&password=123456&scope=all请求响应:

image.png

注意: 1.咱们发现使用受权码模式和使用密码模式获取的access_token是相同的。虽然受权模式不一样,可是使用的都是同一个用户:yxm;同一个用户他在去反复请求access_token时候,Spring Security OAuth会判断当前用户是否是发过响应请求access_token;只要这个token没有过时,只是这个expires_in在变小。
2.如今全部的4种受权都实现了,token的存储也实现了。实际上就用了一行代码:@EnableAuthorizationServer

4.资源服务器

自定义类,添加注解:@EnableResourceServer

@Configuration
@EnableResourceServer
public class MyResourceServerConfig {
    
}

添加以上资源服务器配置以后,咱们访问下以前的用户接口
image.png

报401:

此时没有认证,咱们经过携带下access_token便可访问资源,可是重启服务后access_token已通过期了,咱们须要从新获取access_token而后再携带参数请求。

image.png

image.png

经过token_type与access_token合并。

5. 总结

如今流程走通了,可是仍是存在不少不足:

  1. 受权模式只能是oauth2规定的这4中模式:好比咱们想用手机号加手机验证码获取access_token就行不通。
  2. 咱们用户信息是存放在内存中的,服务器已重启就消失掉了。
  3. 咱们发出去的token也是一个随机的串,咱们可否去定制他。好比用如今流行的JWT方式。
相关文章
相关标签/搜索