使用OAuth2实现认证服务器和资源服务器

在项目中有用到OAuth2,这里记录下研究成功。详细介绍可参考官方文档:https://tools.ietf.org/html/rfc6749

准备工作:

1、spring-oauth-server 认证服务器和资源服务器(也可以分开)。作为一个jar包提供给客户端使用

2、spring-security-demo 客户端。资源所有者,需要依赖spring-oauth-server进行授权认证

spring-oauth-server

pom依赖:

<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>

RedisTokenStore配置:

@Configuration
public class RedisTokenStoreConfig {

    @Autowired
    private RedisConnectionFactory connectionFactory;

    /**
     * 配置Token存储到Redis中
     */
    @Bean
    public TokenStore redisTokenStore() {
        return new RedisTokenStore(connectionFactory);
    }

}

两个配置类SecurityProperty和OAuth2Property:

@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "xwj.security")
public class SecurityProperty {

    private OAuth2Property oauth2 = new OAuth2Property();

}
@Getter
@Setter
public class OAuth2Property {

    private OAuth2ClientProperty[] clients = {};

}

认证服务器配置:

/**
 * 配置认证服务器
 */
@Configuration
@EnableAuthorizationServer // 开启认证服务
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private SecurityProperty securityProperty;
    @Autowired
    private TokenStore tokenStore;

    /**
     * 用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore) // 配置存储token的方式(默认InMemoryTokenStore)
                .authenticationManager(authenticationManager) // 密码模式,必须配置AuthenticationManager,不然不生效
                .userDetailsService(userDetailsService); // 密码模式,这里得配置UserDetailsService

        /*
         * pathMapping用来配置端点URL链接,有两个参数,都将以 "/" 字符为开始的字符串
         *
         * defaultPath:这个端点URL的默认链接
         *
         * customPath:你要进行替代的URL链接
         */
        endpoints.pathMapping("/oauth/token", "/oauth/xwj");
    }

    /**
     * 用来配置客户端详情服务(给谁发送令牌)
     */
    @Override
    public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
        InMemoryClientDetailsServiceBuilder builder = clients.inMemory();
        OAuth2ClientProperty[] oauth2Clients = securityProperty.getOauth2().getClients();
        if (ArrayUtils.isNotEmpty(oauth2Clients)) {
            for (OAuth2ClientProperty config : oauth2Clients) {
                builder // 使用in-memory存储
                        .withClient(config.getClientId()).secret(config.getClientSecret())
                        .accessTokenValiditySeconds(config.getAccessTokenValiditySeconds()) // 发出去的令牌有效时间(秒)
                        .authorizedGrantTypes("authorization_code", "client_credentials", "password", "refresh_token") // 该client允许的授权类型
                        .scopes("all", "read", "write") // 允许的授权范围(如果是all,则请求中可以不要scope参数,否则必须加上scopes中配置的)
                        .autoApprove(true); // 自动审核
            }
        }
    }

}

认证服务器端点配置:

1、token模式默认存储在内存中,服务重启后就没了。这里改为使用redis存储,同时也可用于客户端扩展集群

2、如果要使用密码模式,必须得配置AuthenticationManager(原因可查看源码AuthorizationServerEndpointsConfigurer的getDefaultTokenGranters方法)

3、在使用密码模式时,如果用户实现了UserDetailsService类,则在验证用户名密码时,使用自定义的方法。因为在校验用户名密码时,使用了DaoAuthenticationProvider中的retrieveUser方法(具体可参考AuthenticationManager、ProviderManager

4、默认获取token的路径是/oauth/token,通过pathMapping方法,可改变默认路径

客户端配置:

1、这里是从配置类中读取clientId、clientSecret、有效期等,便于扩展

2、authorizedGrantTypes,授权认证类型,这里配置的是授权码模式、客户端模式、密码模式、刷新token模式(还有一种简化模式,这里不演示)

3、如果不配置autoApprove,那获取授权码时,需要手动点一下授权

资源服务器配置:

@Configuration
@EnableResourceServer // 开启资源服务
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        super.configure(http);
    }

}

使用默认的配置,表示对所有资源都需要授权认证,即授权通过后可以访问所有资源

spring-security-demo

pom依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- druid 数据库连接池 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.0.5</version>
</dependency>
<dependency>
    <groupId>com.xwj</groupId>
    <artifactId>spring-oauth-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

application.yml配置文件:

server:
  port: 80

spring:
  application:
    name: spring-security-demo #应用程序名称
  #durid 数据库连接池
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://127.0.0.1:3306/xwj?autoReconnect=true&failOverReadOnly=false&createDatabaseIfNotExist=true&useSSL=false&useUnicode=true&characterEncoding=utf8
    username: root
    password: 123456
  jpa:
    open-in-view: true
    hibernate:
      ddl-auto: update
      #show-sql: true
    properties:
      hibernate.dialect: org.hibernate.dialect.MySQL57InnoDBDialect
  redis:
    database: 2 #Redis数据库索引(默认为0)
    host: localhost #Redis服务器地址
    port: 6379
    password: ## 密码(默认为空)
    pool:
      max-active: 8 #连接池最大连接数(使用负值表示没有限制)
      max-wait: -1 #连接池最大阻塞等待时间(使用负值表示没有限制)
      max-idle: 8  #连接池中的最大空闲连接

logging:
  level:
    #root: INFO
    #org.hibernate: INFO
    jdbc: off
    jdbc.sqltiming: debug
    com:
      xwj: debug

xwj:
  security:
    oauth2:
      storeType: redis
      jwtSignKey: 1234567890
      clients[0]:
        clientId: test
        clientSecret: testsecret
        accessTokenValiditySeconds: 1800
      clients[1]:
        clientId: myid
        clientSecret: mysecret
        accessTokenValiditySeconds: 3600

新建UserDetailsService的实现类MyUserDetailServiceImpl类:

/**
 * 如果使用密码模式,需要实现UserDetailsService,用于覆盖默认的InMemoryUserDetailsManager方法
 *
 * 可以用来校验用户信息,并且可以添加自定义的用户属性
 */
@Service
public class MyUserDetailServiceImpl implements UserDetailsService {

    @Autowired
    private IUserService userService;

    /**
     * 根据username查询用户实体
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 通过用户名查询数据
        AuthUserInfo userInfo = userService.findByUsername(username);
        if (userInfo == null) {
            throw new BadCredentialsException("User ‘" + username + "‘ not found");
        }

        // 用户角色
        List<? extends GrantedAuthority> authorities = AuthorityUtils
                .commaSeparatedStringToAuthorityList("ROLE_" + userInfo.getRole());

        return new SocialUser(username, userInfo.getPassword(), true, true, true, true, authorities);
    }

}

新建一个密码加密的配置类,用来实现PasswordEncoder(默认的加密方式是BCryptPasswordEncoder)

/**
 * 加密方式配置
 */
@Configuration
public class PasswordEncoderConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence rawPassword) {
                return DigestUtils.md5DigestAsHex(rawPassword.toString().getBytes());
            }

            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                return encodedPassword.contentEquals(encode(rawPassword));
            }
        };
    }

}

用户信息实体:

@Entity
@Getter
@Setter
public class AuthUserInfo {

    @Id
    @TableGenerator(name = "global_id_gen", allocationSize = 1)
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "global_id_gen")
    private Long id;

    /** 用户名 */
    private String username;

    /** 密码 */
    private String password;

    /** 角色 */
    private String role;

}

数据访问层,自己实现。造一条用户数据(角色只能为USER,在获取授权码的时候需要拥有该角色的用户进行表单登录):

新建一个IndexController:

@RestController
@RequestMapping("index")
public class IndexController {

    /**
     * 获取资源
     */
    @GetMapping("/getResource")
    public String getResource() {
        return "OK";
    }

    /**
     * 获取当前授权用户
     */
    @GetMapping("/me")
    public Object getCurrrentUser(@AuthenticationPrincipal UserDetails user) {
        return user;
    }

}

1、授权码模式:

   1.1、 浏览器请求如下地址,获取授权code(其中response_type=code是固定写法,scope为权限,state为自定义数据):

http://localhost/oauth/authorize?client_id=test&redirect_uri=http://www.baidu.com&response_type=code&scope=read
&state=mystate

1.2、输入用户名密码(xwj/123456):

1.3、上面配置的自动授权,所有会oauth会立马调用回调地址并返回授权code和state(可以发现state传的什么就返回什么):

1.4、在获得授权码后,接下来获取访问令牌。使用postman请求  http://localhost/oauth/xwj:

注意,需要在Authorization里设置Username和Password(就是客户端配置的clientId和clientSecret),还有TYPE类型:

获取到的token如下:

1.5、授权码用一次之后,oauth将会把它从缓存中删掉,所以只能使用。如果重复使用,将返回:

1.6、如果不带上token,请求资源:http://localhost/index/getResource,将会返回无权访问:

1.7、如果带上token,请求资源:http://localhost/index/getResource,将可以正常获取资源数据:

1.8、如果token错误,将提示无效的token:

2、客户端模式:

2.1、直接获取token,请求地址:http://localhost/oauth/xwj

获取token操作同上。由于客户端模式每次的参数是一样的,则请求多次返回同一个token,只是有效期在变小

3、密码模式:

3.1、直接获取token,请求地址:http://localhost/oauth/xwj

获取token操作同上。由于客户端模式每次的参数是一样的,则请求多次返回同一个token,只是有效期在变小

4、刷新token:

4.1、以授权码模式为例,在授权码的token过期后,使用当时的refresh_token获取新的token:

4.2、获取到新的token,就可以正常访问资源了:

使用redis存储token,打开Redis Desktop Manager工具,可以看到数据结构如下:

至此,演示完毕~~~

原文地址:https://www.cnblogs.com/xuwenjin/p/12669791.html

时间: 2024-08-29 12:11:19

使用OAuth2实现认证服务器和资源服务器的相关文章

使用Owin中间件搭建OAuth2.0认证授权服务器

前言 这里主要总结下本人最近半个月关于搭建OAuth2.0服务器工作的经验.至于为何需要OAuth2.0.为何是Owin.什么是Owin等问题,不再赘述.我假定读者是使用Asp.Net,并需要搭建OAuth2.0服务器,对于涉及的Asp.Net Identity(Claims Based Authentication).Owin.OAuth2.0等知识点已有基本了解.若不了解,请先参考以下文章: MVC5 - ASP.NET Identity登录原理 - Claims-based认证和OWIN

Spring Cloud微服务安全实战_4-5_搭建OAuth2资源服务器

上一篇搭建了一个OAuth2认证服务器,可以生成token,这篇来改造下之前的订单微服务,使其能够认这个token令牌. 本篇针对订单服务要做三件事: 1,要让他知道自己是资源服务器,他知道这件事后,才会在前边加一个过滤器去验令牌(配置@EnableResourceServer 配置类) 2,要让他知道自己是什么资源服务器(配置资源服务器ID) 3,配置去哪里验令牌,怎么验令牌,要带什么信息去验 (配置@EnableWebSecurity 配置TokenServices,配置Authentica

spring security oauth2 jwt 认证和资源分离的配置文件(java类配置版)

最近再学习spring security oauth2.下载了官方的例子sparklr2和tonr2进行学习.但是例子里包含的东西太多,不知道最简单最主要的配置有哪些.所以决定自己尝试搭建简单版本的例子.学习的过程中搭建了认证和资源在一个工程的例子,将token存储在数据库的例子等等 .最后做了这个认证和资源分离的jwt tokens版本.网上找了一些可用的代码然后做了一个整理, 同时测试了哪些代码是必须的.可能仍有一些不必要的代码在,欢迎大家赐教. 一.创建三个spring boot 工程,分

DotNetOpenAuth实践之WebApi资源服务器

系列目录: DotNetOpenAuth实践系列(源码在这里) 上篇我们讲到WCF服务作为资源服务器接口提供数据服务,那么这篇我们介绍WebApi作为资源服务器,下面开始: 一.环境搭建 1.新建WebAPI项目 2.利用Nuget添加DotNetOpenAuth 注意: Nuget里面的 NotNetOpenAuth 5.0.0 alpha3有bug,要到github(DotNetOpenAuth)里面下源码自己编译,用编译的dll替换掉Nuget引用的dll 3.把上次制作的证书文件拷贝的项

基于C# Socket的Web服务器---静态资源处理

Web服务器是Web资源的宿主,它需要处理用户端浏览器的请求,并指定对应的Web资源返回给用户,这些资源不仅包括HTML文件,JS脚本,JPG图片等,还包括由软件生成的动态内容.为了满足上述需求,一个完整的Web服务器工作流程: 1)   服务器获得浏览器通过TCP/IP连接向服务器发送的http请求数据包. 2)   HTTP请求经过Web服务器的HTTP解析引擎分析得出请求方法.资源地址等信息,然后开始处理. 3)   对于静态请求,则在服务器上查询请求url路径下文件,并返回(如果未找到则

TCP/IP协议学习(四) 基于C# Socket的Web服务器---静态资源处理

目录 1. C# Socket通讯 2. HTTP 解析引擎 3. 资源读取和返回 4. 服务器测试和代码下载 Web服务器是Web资源的宿主,它需要处理用户端浏览器的请求,并指定对应的Web资源返回给用户,这些资源不仅包括HTML文件,JS脚本,JPG图片等,还包括由软件生成的动态内容.为了满足上述需求,一个完整的Web服务器工作流程: 1) 服务器获得浏览器通过TCP/IP连接向服务器发送的http请求数据包. 2) HTTP请求经过Web服务器的HTTP解析引擎分析得出请求方法.资源地址等

Kerberos认证协议中TGS服务器可以去掉吗?

Kerberos协议最早是由MIT提出的,是一种身份认证协议. 应用场景:在一个开放环境中,一个工作站用户想通过网络对分布在网络中的各种服务提出请求,那么希望服务器能够只对授权用户提供服务,并能够鉴别服务请求的种类. Kerberos协议的原理:Kerberos通过提供一个集中的授权服务器来负责用户对服务器的认证和服务器对用户的认证,而不是为每个服务器提供详细的认证协议. Kerberos名词: Client:用户. AS:认证服务器,可以通过查询数据库,判断用户的口令,从而为用户颁发票据授权票

Unity3d 从资源服务器下载资源(一)

项目里面的许多资源都是从资源服务器加载的,这样子可以减小客户端的包大小. 所以我们需要一个专门的类来管理下载资源. 资源分很多类型,如:json表,txt文件,image文件,二进制文件,UIAtlas图集,AssetBundle等. 所以,首先创建一个管理资源文件类型的类LoadFileType. 其中文件类型可以用枚举来表示,也可以用类成员常量来表示. 此处使用类成员常量: 1 using UnityEngine; 2 using System.Collections; 3 4 5 name

配置Xshell或xftp使用密钥认证方式登录到服务器

配置Xshell或xftp使用密钥认证方式登录到服务器:  Xshell及xftp下载地址\\**.**.*.*\soft\sftp   (帐号密码只需设置一次,即两个软件即可使用)        1.在xshell  工具栏下选择用户密钥管理器,导入邮件附件里的私钥文件即单独的用户名文件.        2.打开Xshell,点击"New"按钮,弹出"New Session Properties"对话框,在"Connection"栏目中,输入刚