spring cloud oauth2 实现用户认证登录的示例代码

需求

在微服务架构中,我们有很多业务模块,每个模块都需要有用户认证,权限校验。有时候也会接入来自第三方厂商的应用。要求是只登录一次,即可在各个服务的授权范围内进行操作。看到这个需求,立马就想到了这不就是单点登录吗?于是基于这样的需求,作者使用spring-cloud-oauth2去简单的实现了下用户认证和单点登录。

相关介绍

OAuth2

OAuth2是一个关于授权的网络标准,他定制了设计思路和执行流程。OAuth2一共有四种授权模式:授权码模式(authorization code)、简化模式(implicit)、密码模式(resource owner password)和客户端模式(client credentials)。数据的所有者告诉系统同意授权第三方应用进入系统,获取这些数据。于是数据所有者生产了一个短时间内有效的授权码(token)给第三方应用,用来代替密码,供第三方使用。具体流程请看下图,具体的OAuth2介绍,可以参考这篇文章,写的很详细。(https://www.jb51.net/article/198292.htm)

Token

令牌(token)和密码(password)的作用是一样的,都可以进入系统获取资源,但是也有几点不同:

  1. 令牌是短期的,到期会自动失效,用户无法修改。密码是长期的,用户可以修改,如果不修改,就不会发生变化。
  2. 令牌可以被数据所有者撤销,令牌会立即失效。密码一般不允许其他人撤销,只能被操作权限更高的人或者本人修改/重制。
  3. 令牌是有权限范围的,会被数据所有者授予。

实现的功能

本篇介绍的是通过密码模式来实现单点登录的功能。

​ 在微服务架构中,我们的一个应用可能会有很多个服务运行,协调来处理实际的业务。这就需要用到单点登录的技术,来统一认证调取接口的是哪个用户。那总不能请求一次,就认证一次,这么做肯定是不行的。那么就需要在认证完用户之后,给这个用户授权,然后发一个令牌(token),有效期内用户请求资源时,就只需要带上这个标识自己身份的token即可。

架构说明
认证中心:oauth2-oauth-server,OAuth2的服务端,主要完成用户Token的生成、刷新、验证等。

微服务:mzh-etl,微服务之一,接收到请求之后回到认证中心(oauth2-oauth-server)去验证。

代码实现

使用到的框架是java基础的spring boot 和spring-cloud-oauth2

认证中心:

1、引入需要的maven包

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

因为spring-cloud-starter-oauth2中包含了spring-cloud-starter-security,所以就不用再单独引入了,引入redis包是为了使用redis来存储token。

2、配置application.yml

这里主要用到的是redis的配置,mysql数据库的配置暂时没有用到。

spring:
 application:
 name: oauth-server
 datasource:
 url: jdbc:mysql://localhost:3306/mzh_oauth?useSSL=false&characterEncoding=UTF-8
 username: root
 password: admin123
 driver-class-name: com.mysql.jdbc.Driver
 hikari:
  connection-timeout: 30000
  idle-timeout: 600000
  max-lifetime: 1800000
  maximum-pool-size: 9
 redis:
 database: 0
 host: localhost
 port: 6379
 jedis:
  pool:
  max-active: 8
  max-idle: 8
  min-idle: 0
 timeout: 10000
server:
 port: 8888
 use-forward-headers: true

management:
 endpoint:
 health:
  enabled: true

3、spring security 权限配置

需要继承WebSecurityConfigurerAdapter

/**
 * @Author mzh
 * @Date 2020/10/24
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

 @Autowired
 private CustomUserDetailsService customUserDetailsService;

 /**
  * 修改密码的加密方式
  * @return
  */
 @Bean
 public PasswordEncoder passwordEncoder(){
  return new BCryptPasswordEncoder();
 }

 @Bean
 @Override
 public AuthenticationManager authenticationManagerBean() throws Exception{
  return super.authenticationManagerBean();
 }

 @Override
 protected void configure(AuthenticationManagerBuilder auth) throws Exception{
  // 如果使用BCryptPasswordEncoder,这里就必须指定密码的加密类
  auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
 }

 @Override
 protected void configure(HttpSecurity http) throws Exception {
  http.authorizeRequests()
    .antMatchers("/oauth/**").permitAll();
 }
}

BCryptPasswordEncoder是一个不可逆的密码加密类,AuthenticationManager是OAuth2的password必须指定的授权管理Bean。

CustomUserDetailsService这个类是被注入进来的,熟悉spring security的同学应该知道,spring security有一个自己的UserdetailsService用于权限校验时获取用户信息,但是很多时候不符合我们的业务场景,就需要重现实现这个类。

4、实现CustomUserDetailsService

UserDetailsService这个类的核心方法就是loadUserByUsername()方法,他接收一个用户名,返回一个UserDetails对象。

/**
 * @Author mzh
 * @Date 2020/10/24
 */
@Component(value = "customUserDetailsService")
public class CustomUserDetailsService implements UserDetailsService {

 @Autowired
 private PasswordEncoder passwordEncoder;

 @Override
 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  // 1. 根据username 去数据库查询 user

  // 2.获取用户的角色和权限

  // 下面是写死,暂时不和数据库交互
  if(!(("admin").equals(username))){
   throw new UsernameNotFoundException("the user is not found");
  }else{
   String role = "ADMIN_ROLE";
   List<SimpleGrantedAuthority> authorities = new ArrayList<>();
   authorities.add(new SimpleGrantedAuthority(role));
   String password = passwordEncoder.encode("123456");
   return new User(username,password,authorities);
  }
 }
}

这里是在程序中写死了用户和权限。账号:admin,密码:123456,权限:ADMIN_ROLE(注意是权限,不是角色),实际中应该从数据库获取用户和相关的权限,然后进行认证。

5、OAuth2 配置

OAuth2配置需要继承AuthorizationServerConfigurerAdapter类

/**
 * @Author mzh
 * @Date 2020/10/24
 */
@Configuration
@EnableAuthorizationServer
public class Oauth2Config extends AuthorizationServerConfigurerAdapter {

 @Autowired
 private PasswordEncoder passwordEncoder;

 @Autowired
 private UserDetailsService customUserDetailsService;

 @Autowired
 private AuthenticationManager authenticationManager;

 @Autowired
 private TokenStore redisTokenStore;

 /**
  * 对AuthorizationServerEndpointsConfigurer参数的重写
  * 重写授权管理Bean参数
  * 重写用户校验
  * 重写token缓存方式
  * @param endpointsConfigurer
  * @throws Exception
  */
 @Override
 public void configure(final AuthorizationServerEndpointsConfigurer endpointsConfigurer) throws Exception{
  endpointsConfigurer.authenticationManager(authenticationManager)
    .userDetailsService(customUserDetailsService)
    .tokenStore(redisTokenStore);
 }

 /**
  * 客户端的参数的重写
  * 这里是将数据直接写入内存,实际应该从数据库表获取
  * clientId:客户端Id
  * secret:客户端的密钥
  * authorizedGrantTypes:授权方式
  *  authorization_code: 授权码类型,
  *  implicit: 隐式授权,
  *  password: 密码授权,
  *  client_credentials: 客户端授权,
  *  refresh_token: 通过上面4中方式获取的刷新令牌获取的新令牌,
  *      注意是获取token和refresh_token之后,通过refresh_toke刷新之后的令牌
  * accessTokenValiditySeconds: token有效期
  * scopes 用来限制客户端访问的权限,只有在scopes定义的范围内,才可以正常的换取token
  * @param clients
  * @throws Exception
  */
 @Override
 public void configure(ClientDetailsServiceConfigurer clients) throws Exception{
  clients.inMemory()
    .and()
    .withClient("mzh-etl")
    .secret(passwordEncoder.encode("mzh-etl-8888"))
    .authorizedGrantTypes("refresh_token","authorization_code","password")
    .accessTokenValiditySeconds(3600)
    .scopes("all");
 }

 @Override
 public void configure(AuthorizationServerSecurityConfigurer serverSecurityConfigurer) throws Exception{
  serverSecurityConfigurer.allowFormAuthenticationForClients();
  serverSecurityConfigurer.checkTokenAccess("permitAll()");
  serverSecurityConfigurer.tokenKeyAccess("permitAll()");
  serverSecurityConfigurer.passwordEncoder(passwordEncoder);
 }
}

6、启动服务

上述步骤完成之后启动服务,然后观察IDEA下方的Endpoints中的Mappings,就可以找到相关的认证端口。主要的有以下几个:

  • POST /oauth/authorize  授权码模式认证授权接口
  • GET/POST /oauth/token  获取 token 的接口
  • POST  /oauth/check_token  检查 token 合法性接口

到此,认证中心就算是创建完成了。我们通过idea的REST Client 来请求一个token进行测试。

请求内容如下:

POST http://localhost:8888/oauth/token?grant_type=password&username=admin&password=123456&scope=all
Accept: */*
Cache-Control: no-cache
Authorization: Basic dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==

第一行POST http://localhost:8888/oauth/token?grant_type=password&username=admin&password=123456&scope=all 表示发起一个POST请求,请求路径是/oauth/token,请求参数是grant_type=password表示认证类型是password,username=admin&password=123456表示用户名是admin,密码是123456,scope=all是权限相关的,之前在Oauth2Config中配置了scope是all。

第四行表示在请求头中加入一个字段Authorization,值为Basic空格base64(clientId:clientSecret),我们之前配置的clientId是“meh-etl”,clientSecret是"meh-etl-8888",所以这个值的base64是:bXpoLWV0bDptemgtZXRsLTg4ODg=。

运行请求之后,如果参数都正确的话,获取到返回的内容如下:

{
 // token值,后面请求接口时都需要带上的token
 "access_token": "b4cb804c-93d2-4635-913c-265ff4f37309",
 // token的形式
 "token_type": "bearer",
 // 快过期时可以用这个换取新的token
 "refresh_token": "5cac05f4-158f-4561-ab16-b06c4bfe899f",
 // token的过期时间
 "expires_in": 3599,
 // 权限范围
 "scope": "all"
}

token值过期之后,可以通过refresh_token来换取新的access_token

POST http://localhost:8888/oauth/token?grant_type=refresh_token&refresh_token=706dac10-d48e-4795-8379-efe8307a2282
Accept: */*
Cache-Control: no-cache
Authorization: Basic dXNlci1jbGllbnQ6dXNlci1zZWNyZXQtODg4OA==

这次grant_type的值为“refresh_token”,refresh_token的值是要过期的token的refresh_token值,也就是之前请求获取Token的refresh_token值,请求之后会返回一个和获取token时一样格式的数据。

微服务

1、引入需要的maven包

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、配置application.yml

spring:
 application:
 name: mzh-etl
 redis:
 database: 1
 host: localhost
 port: 6379
 jedis:
  pool:
  max-active: 8
  max-idle: 8
  min-idle: 0
 timeout: 10000
server:
 port: 8889
security:
 oauth2:
 client:
  # 需要和之前认证中心配置中的一样
  client-id: mzh-etl
  client-secret: mzh-etl-8888
  # 获取token的地址
  access-token-uri: http://localhost:8888/oauth/token
 resource:
  id: mzh-etl
  user-info-uri: user-info
 authorization:
  # 检查token的地址
  check-token-access: http://localhost:8888/oauth/check_token

这里的配置一定要仔细,必须和之前认证中心中配置的一样。

3、资源配置

在OAuth2中接口也称为资源,资源的权限也就是接口的权限。spring-cloud-oauth2提供了关于资源的注解

@EnableResourceServer

/**
 * @Author mzh
 * @Date 2020/10/24
 */
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

 @Value("${security.oauth2.client.client-id}")
 private String clientId;

 @Value("${security.oauth2.client.client-secret}")
 private String clientSecret;

 @Value("${security.oauth2.authorization.check-token-access}")
 private String checkTokenEndpointUrl;

 @Autowired
 private RedisConnectionFactory redisConnectionFactory;

 @Bean("redisTokenStore")
 public TokenStore redisTokenStore(){
  return new RedisTokenStore(redisConnectionFactory);
 }

 @Bean
 public RemoteTokenServices tokenService() {
  RemoteTokenServices tokenService = new RemoteTokenServices();
  tokenService.setClientId(clientId);
  tokenService.setClientSecret(clientSecret);
  tokenService.setCheckTokenEndpointUrl(checkTokenEndpointUrl);
  return tokenService;
 }

 @Override
 public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
  resources.tokenServices(tokenService());
 }

 @Override
 public void configure(HttpSecurity http) throws Exception {
  http.authorizeRequests().antMatchers("/get/**").authenticated();
 }
}

4、创建一个接口

@RestController
public class UserController {

 @GetMapping("get")
 @PreAuthorize("hasAuthority('ADMIN_ROLE')")
 public Object get(Authentication authentication){
  authentication.getAuthorities();
  OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
  String token = details.getTokenValue();
  return token;
 }
}

这个接口就是会返回一个请求他时携带的token值,@PreAuthorize会在请求接口时检查是否用权限“ADMIN_ROLE”(之前认证中心配置的权限)

5、启动服务

启动服务,只有当用户有“ADMIN_ROLE“的时候,才能正确返回,否则返回401未授权

同样适用REST Client来发起一个请求:

GET http://localhost:8889/get
Accept: */*
Cache-Control: no-cache
Authorization: bearer b4cb804c-93d2-4635-913c-265ff4f37309

请求路径是http://localhost:8889/get 然后在请求头部带上我们上一步骤获取到的token,放入到Authorization中,格式是bearer空格token值,如果请求成功,就会把token原样返回。

到此这篇关于spring cloud oauth2 实现用户认证登录的示例代码的文章就介绍到这了,更多相关spring cloud oauth2 认证登录内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Spring Cloud OAuth2 实现用户认证及单点登录的示例代码

    OAuth 2 有四种授权模式,分别是授权码模式(authorization code).简化模式(implicit).密码模式(resource owner password credentials).客户端模式(client credentials),具体 OAuth2 是什么,可以参考这篇文章.(http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html) 本文我们将使用授权码模式和密码模式两种方式来实现用户认证和授权管理. OAuth2 其

  • Spring Cloud下OAUTH2注销的实现示例

    接上文Spring Cloud下基于OAUTH2认证授权的实现,我们将基于Spring Cloud实现OAUTH2的注销功能. 1 增加自定义注销Endpoint 所谓注销只需将access_token和refresh_token失效即可,我们模仿org.springframework.security.oauth2.provider.endpoint.TokenEndpoint写一个使access_token和refresh_token失效的Endpoint: @FrameworkEndpoi

  • Spring Cloud下基于OAUTH2认证授权的实现示例

    在Spring Cloud需要使用OAUTH2来实现多个微服务的统一认证授权,通过向OAUTH服务发送某个类型的grant type进行集中认证和授权,从而获得access_token,而这个token是受其他微服务信任的,我们在后续的访问可以通过access_token来进行,从而实现了微服务的统一认证授权. 本示例提供了四大部分: discovery-service:服务注册和发现的基本模块 auth-server:OAUTH2认证授权中心 order-service:普通微服务,用来验证认

  • spring cloud oauth2 实现用户认证登录的示例代码

    需求 在微服务架构中,我们有很多业务模块,每个模块都需要有用户认证,权限校验.有时候也会接入来自第三方厂商的应用.要求是只登录一次,即可在各个服务的授权范围内进行操作.看到这个需求,立马就想到了这不就是单点登录吗?于是基于这样的需求,作者使用spring-cloud-oauth2去简单的实现了下用户认证和单点登录. 相关介绍 OAuth2 OAuth2是一个关于授权的网络标准,他定制了设计思路和执行流程.OAuth2一共有四种授权模式:授权码模式(authorization code).简化模式

  • Spring Cloud 系列之负载均衡 Ribbon的示例代码

    1.1 简介 1.1.1 概述   Ribbon 是 Netflix 发布的负载均衡器,它有助于控制 HTTP 和 TCP 客户端的行为.为 Ribbon 配置服务提供者地址列表后,Ribbon 就可基于某种负载均衡算法,自动地帮助服务消费者去请求.Ribbon 默认为我们提供了很多的负载均衡算法,例如轮询.随机等.当然,我们也可为 Ribbon 实现自定义的负载均衡算法.Ribbon 现在已经进入维护状态,但目前仍在大规模使用,Spring Cloud 准备使用 LoadBalancer 作为

  • Spring cloud oauth2如何搭建认证资源中心

    一 认证中心搭建 添加依赖,如果使用spring cloud的话,不管哪个服务都只需要这一个封装好的依赖即可 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> 配置spring security /** * security配置类 */

  • spring + shiro + cas 实现sso单点登录的示例代码

    sso-shiro-cas spring下使用shiro+cas配置单点登录,多个系统之间的访问,每次只需要登录一次,项目源码 系统模块说明 1.cas: 单点登录模块,这里直接拿的是cas的项目改了点样式而已 2.doc: 文档目录,里面有数据库生成语句,采用的是MySQL5.0,数据库名为db_test 3.spring-node-1: 应用1 4.spring-node-2: 应用2 其中node1跟node2都是采用spring + springMVC + mybatis 框架,使用ma

  • spring boot整合Shiro实现单点登录的示例代码

    Shiro是什么 Shiro是一个Java平台的开源权限框架,用于认证和访问授权.具体来说,满足对如下元素的支持: 用户,角色,权限(仅仅是操作权限,数据权限必须与业务需求紧密结合),资源(url). 用户分配角色,角色定义权限. 访问授权时支持角色或者权限,并且支持多级的权限定义. Q:对组的支持? A:shiro默认不支持对组设置权限. Q:是否可以满足对组进行角色分配的需求? A:扩展Realm,可以支持对组进行分配角色,其实就是给该组下的所有用户分配权限. Q:对数据权限的支持? 在业务

  • spring cloud 配置阿里数据库连接池 druid的示例代码

    1.配置pom <!-- druid 数据库连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.12</version> <exclusions> <exclusion> <groupId>com.alibaba</groupId&

  • spring cloud oauth2 feign 遇到的坑及解决

    目录 springcloudoauth2feign遇到的坑 客户端模式 基于springsecurity springcloud微服务增加oauth2权限后feign调用报null 一般是这样实现的 spring cloud oauth2 feign 遇到的坑 关于oauth2相关的内容这里不重复描述,在spring cloud中在管理内部api时鉴权相信有很多人会有疑问,这里描述两种比较low的用法,由于公司内部使用的是阿里云edas这里仅仅是记录一下,如果有更好的用法在请赐教,不喜勿喷! 客

  • Spring Cloud下实现用户鉴权的方案

    目录 一.整体架构 二.实现步骤 三.其它问题 四.完整代码 Java下常用的安全框架主要有Spring Security和shiro,都可提供非常强大的功能,但学习成本较高.在微服务下鉴权多多少少都会对服务有一定的入侵性. 为了降低依赖,减少入侵,让鉴权功能相对应用服务透明,我们采用网关拦截资源请求的方式进行鉴权. 一.整体架构 用户鉴权模块位于API GateWay服务中,所有的API资源请求都需要从此通过. 做身份认证,通过则缓存用户权限数据,不通过返回401 做用户鉴权,比对当前访问资源

  • Spring Cloud oauth2 认证服务搭建过程示例

    目录 安装httpie 导入数据库脚本 sts中导入项目 修改 POM文件 修改配置文件 修改主类文件 编译,运行 测试 查看Redis缓存 安装httpie 安装httpie 需要 python 环境 pip install --upgrade httpie 进入D:\Project目录,在此目录下打开CMD,调用httpie,创建 oauth2 项目 http -d https://start.spring.io/starter.zip javaVersion==17 groupId==co

随机推荐