前面讲了什么是OAuth协议,今天我们来结合Spring Security来搭建一个授权的demo.
我们会用postman模拟登陆
OAuthServer
pom依赖
<!--引入security-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-security</artifactId>
</dependency>
<!--引入oauth-->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
oauthserver引导类开启注解
引导类开启了@EnableAuthorizationServer注解,表名这是一个OAuth认证服务器,该注解会默认开放几个端点,而我在引导类里也手动添加了一个/user端点,这几个端点我们将在OAuth2.0认证和授权的时候使用。
注册客户端应用程序
我们目前已经有认证服务了,但是还没有在认证服务中注册任何应用程序、用户或角色。我们可以通过显示的声明方式告诉认证服务哪个客户端是认证通过的。
在这里我们要明确区分一个概念,认证不等于授权。认证是用户通过提交凭证来证明他们是谁的行为,而授权决定是否用户允许做他们想做的事情。
我们创建一个OAuth2Config类,继承AuthorizationServerConfigurerAdapter,并重写他的两个方法。AuthorizationServerConfigurerAdapter这个类是Spring Security的核心部分,它提供了执行关键验证和授权的基本机制。
@Configuration
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
@Autowired
protected AuthenticationManager authenticationManagerBean;
@Autowired
protected UserDetailsService userDetailsServiceBean;
//配置客户端详情信息
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//内存存储
clients.inMemory()
//client_id
.withClient("client")
//client_secret
.secret("secret")
//授权类型
.authorizedGrantTypes("password")
//授权范围
.scopes("app");
}
//配置授权以及令牌的访问端点和令牌服务
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception{
endpoints
.authenticationManager(authenticationManagerBean)
.userDetailsService(userDetailsServiceBean);
}
}
我们重写了两个参数不同的configure方法,第一个参数是ClientDetailsServiceConfigurer 的方法定义了通过验证服务器注册了哪些应用程序。在这个方法中我们做的事情就是规定了哪些应用程序能够允许访问OAuth2保护起来的服务。对于客户端信息,ClientDetailsServiceConfigurer 类支持两种不同的存储方式:1、内存存储。2、JDBC存储。本例我们使用内存存储。
withClient和secret定义了 应用程序的名称和秘钥
authorizedGrantTypes定义了授权类型,该类型一定是OAuth支持的类型。
scopes定义了服务在通过OAuth2认证后,拿到令牌后可以操作的范围。
我们已经定义了应用程序级别的名称和秘钥,现在要创建个人用户以及所属的角色。Spring可以从内存数据存储,支持JDBC的关系数据库中检索用户信息。我们使用内存数据存储来定义用户信息。
我们新建两个用户pk1,pk2,分别分配user,{user,admin}角色,密码分别为password1,password2,要配置OAuth2服务器验证用户ID,要新建一个类并继承WebSecurityConfigurerAdapter类。WebSecurityConfigurerAdapter也是Spring Security的核心类。
@Configuration
public class WebSecurityConfigurerAdapter extends
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter {
//处理验证
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
//处理返回的用户信息
@Override
@Bean
public UserDetailsService userDetailsServiceBean() throws Exception{
return super.userDetailsServiceBean();
}
//不进行密码加密
@Bean
public static PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.inMemoryAuthentication()
.withUser("pk1")
.password("password1")
.roles("USER")
.and()
.withUser("pk2")
.password("password2")
.roles("USER","ADMIN");
}
}
我们要通过Spring Security为OAuth2服务器提供一种验证用户的机制,并返回正在验证的用户的信息。这通过我们两个@Bean来实现。这两个bean通过使用父类WebSecurityConfigurerAdapter 中的默认验证方法来公开。在之前的OAuth2Config类中我们看到,我们自动注入了这两个bean,并且在
中配置了。这两个bean用于配置端点/oauth/token和/user端点,这个稍后我们讲解。
我们现在已经做好了整个验证授权的流程,可以来做测试了,我们使用postman发送post请求到OAuth2认证服务器获取用户凭证(token),然后我们再次请求资源的时候带上我们的凭证去获取资源。接下来我们启动我们的OAuth2服务器
控制台会打印几个端点
/oauth/token 这个是我们请求令牌的端点
然后我们用POSTMAN模拟用户提交信息去请求令牌
可以看到我们成功获取了令牌信息
我们获取到了有效的令牌后,我们就可以使用验证服务中公开的/user端点来检索与令牌相关联的用户信息了,后续我们的例子都将调用认证服务/user端点来确认令牌并检索用户信息。
下面那我们访问/user端点,并附带上我们的令牌信息
记住,在任何时候我们访问受OAuth2保护的端点都要携带我们的有效令牌,我们要创建一个名为Authorization的HTTP首部,并赋值为Bearer XXXXX(我们的有效令牌) 来访问端点。看到返回了我们之前申请令牌时候的提交的用户信息。
到目前为止我们已经走通了用户从OAuth2服务器获取令牌的流程,那么我们接下来就要探索怎么通过OAuth2服务器来保护我们的资源。
我们的流程是这样。第一步,用户拿着自己的信息去OAuth2服务器请求令牌,然后携带令牌去请求受保护的服务,第二部,受保护的服务会拿着我们携带的令牌请求OAuth2服务器去验证我们的账号信息,如果令牌有效且符合自身访问规则,那么就让请求通过。这里说的符合自身访问规则是由服务自身决定配置的。
新建服务SERVER1当做我们的资源服务器
pom文件引入依赖
<!--引入security-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
配置文件里配置OAuth2认证服务器
security:
oauth2:
resource:
user-info-uri: http://localhost:8005/user #oauth认证服务器地址 受保护的资源会拦截所有请求并检索http头部的token
给我们的服务引导类加上@EnableResourceServer注解,表明这是一个受保护的资源服务。这个注解会强制执行一个过滤器,该过滤器会拦截所有对该服务调用的请求,检查HTTP首部是否存在OAuth2令牌,然后调用回调地址查看令牌是否有效,该注解还可以控制访问规则,来控制什么人可以访问服务,是否有权限访问服务。
下来我们来围绕服务定义访问控制规则,要定义访问规则,需要扩展ResourceServerConfigurerAdapter类,并覆盖configure方法。所有访问规则都是通过HttpSecurity对象来配置的。
@Configuration
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception{
//只要code没毛病,都可以访问
// http.authorizeRequests().anyRequest().authenticated();
//限制ADMIN角色可以访问,并且是GET请求
http
.authorizeRequests()
//设置对http动词和路径的限制
.antMatchers(HttpMethod.GET,"/hello")
//对权限角色的限制
.hasRole("ADMIN")
.anyRequest()
//都要通过验证
.authenticated();
}
}
上面是我们自定义的配置规则类,第一行配置的是粗粒度的,任何请求只要令牌有效都可以访问该服务的任何端点。第二行我们进行细粒度的过滤,我们要求在访问/hello端点并且是GET请求,角色必须是ADMIN才可以访问。我们启动服务
我们拿着上一步我们通过pk1,权限为user的令牌去访问SERVER1。
看到返回的信息,Access is denied. 拒绝我们的请求 ,因为我们之前pk1角色是user角色,而我们SERVER1的/hello端点要求的是admin角色,所以无法通过我们的规则。我再次替换pk2去请求令牌,并在此访问我们的SERVER1 /hello 端点。
可以看到成功访问了/hello端点。
以上几个简单的例子就成功搭建了我们的授权认证服务器和受保护资源的一套流程。