SpringSecurityOauth2 应用及分析

介绍:Spring Security Oath2是Oath2标准的一种实现,是基于Spring Security框架的封装。

模块一 前提知识:
  1. Oath2.0标准
    Oath是一种标准,它规定了第三方应用访问本应用资源协议方法,目前版本为2.0。
    Oath2 大致流程为:客户端访问应用资源,需先在资源提供方进行认证得到令牌,之后持令牌范文资源提供方的资源信息。认证服务和资源服务可为同一个服务,也可为不同独立服务。
    Oath2 提供了四种获取令牌的模式:授权码模式(authorization code)、简化模式(implicit)、密码模式(resource owner password credentials)、客户端模式(client credentials)
    具体理解Oath2.0原理可参考阮一峰大神博客
  2. Spring Security应用
    SpringSecurity是基于Filter和AOP来对Spring应用进行保护的一套框架,它可对请求级别和方法级别进行安全保护。
    其应用及分析可参考另一篇博客,SpringSecurity初级应用及分析
模块二 组件使用:
  1. 认证服务器
     a 内存保存访问令牌
        i. 引入Oath2的依赖,SpringBoot版本为最新Greenwich
<properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>Greenwich.SR2</spring-cloud.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

    II. 配置访问服务的认证,即配置Spring Security(这里登录账号密码为从数据库加载)

/**
 * 根据用户名加载登陆账号信息
 */
@Service
public class SysUserService implements UserDetailsService {

    @Autowired
    private UserDao userDao;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser user = userDao.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户名不存在");
        }
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        if (!StringUtils.isEmpty(user.getRoles())) {
            String[] roles = user.getRoles().split(",");
            for (String role : roles) {
                if (!StringUtils.isEmpty(role)) {
                    authorities.add(new SimpleGrantedAuthority(role.trim()));
                }
            }
        }
        //这里可直接让SysUser继承实现UserDetails接口,或者使用SpringSecurity提供的User类
        return new User(user.getUsername(), user.getPassword(), authorities);
    }
}
/**
 * 配置Spring Security
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private SysUserService sysUserService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(sysUserService);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/oauth/check_token");
    }



    @Bean
    public BCryptPasswordEncoder createPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

    III. 配置访问令牌的认证,即配置AuthorizationServerConfigurerAdapter

/**
 * 配置内存存储Token令牌
 */
@Configuration
@EnableAuthorizationServer
public class MemoryAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    private final String clientId = "sysclientId";

    private final String secretId = "syssecretId";

    private final String authorizedGrantTypes = "authorization_code";

    private final String scopes = "applist";

    private final String redirectUri = "http://localhost:9001/hello";

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
            .withClient(clientId)
            .secret(createPasswordEncoder().encode(secretId))
            .authorizedGrantTypes(authorizedGrantTypes)
            .scopes(scopes)  //申请的权限列表,会直接展示在申请页面上
            .redirectUris(redirectUri);
    }

    /**
     * 必须显示定义加密编码器
     * @return
     */
    @Bean
    public BCryptPasswordEncoder createPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

    iv. 启动应用程序,spring security oath2 会开放一些endpoints,如下:

/oauth/authorize:授权
/oauth/token:令牌
/oauth/error:授权服务错误信息
/oauth/check_token:令牌解析校验

      首先访问授权endpoint去获取授权码,注意sysclientid替换成自己配置的clientid

http://localhost:8080/oauth/authorize?client_id=sysclientid&response_type=code

      程序默认生成如下授权页面,可以自定义,其中的applist就是上面定义的scopes授权信息。
在这里插入图片描述
      点击Approve授权并Authorize之后,会重定向到定义的redirectUri并在请求参数上新增了参数code=3kTpl3,如下图:
在这里插入图片描述
       v. 接着Post方式访问令牌接口/oauth/token 请求令牌,请求参数为grant_type和code,这里使用Postman模拟请求,地址为如下,注意替换自己的clientId和secretId

http://sysclientId:syssecretId@localhost:8081/oauth/token

       结果如下,可以获取到bearer类型的Token:
在这里插入图片描述 b 数据库保存访问令牌
      I. 由于令牌为数据库保存令牌,所以和内存保存令牌流程区别为配置数据库相关,首先额外引入数据源依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

      II. 配置访问服务的认证,配置同上。
      III. 配置访问令牌的认证,即配置AuthorizationServerConfigurerAdapter,这里需引入数据源,使用的是Hikari数据源。

/**
 * 配置数据库存储令牌
 */
@Configuration
@EnableAuthorizationServer
public class JDBCAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    /**
     * 配置获取Token令牌为数据库获取
     * @return
     */
    @Bean
    public TokenStore  tokenStore() {
        return new JdbcTokenStore(dataSource);
    }

    /**
     * 配置获取Client客户端信息为数据库获取
     * @return
     */
    public ClientDetailsService clientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService());
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore());
    }
}

      IV. 配置数据源信息,注意配置时区信息,mysql 6.0以上都需要配置,这里配置的为北京(东八区)时间。

spring:
  application:
    name: authorization-module

  datasource:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://IP:端口号/数据库?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
      username: ******
      password: ******
      # Hikari数据源配置
      hikari.maximum-pool-size: 20
      hikari.minimum-idle: 5
server:
  port: 8081
logging:
  level=Debug

      V. 新建存储Token相关的数据库,参考官方推荐建表,可直接执行以下SQL:

-- 参考官方推荐建表  https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql
-- 注意替换原来HSQL的LONGVARBINARY类型为MySQL的BLOB类型(注意不是MYSQL的TEXT类型!)
-- used in tests that use HSQL
drop table if exists oauth_client_details; 
create table oauth_client_details (
  client_id VARCHAR(256) PRIMARY KEY,
  resource_ids VARCHAR(256),
  client_secret VARCHAR(256),
  scope VARCHAR(256),
  authorized_grant_types VARCHAR(256),
  web_server_redirect_uri VARCHAR(256),
  authorities VARCHAR(256),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additional_information VARCHAR(4096),
  autoapprove VARCHAR(256)
);

drop table if exists oauth_client_token;
create table oauth_client_token (
  token_id VARCHAR(256),
  token BLOB,
  authentication_id VARCHAR(256) PRIMARY KEY,
  user_name VARCHAR(256),
  client_id VARCHAR(256)
);

drop table if exists oauth_access_token;
create table oauth_access_token (
  token_id VARCHAR(256),
  token BLOB,
  authentication_id VARCHAR(256) PRIMARY KEY,
  user_name VARCHAR(256),
  client_id VARCHAR(256),
  authentication BLOB,
  refresh_token VARCHAR(256)
);

drop table if exists oauth_refresh_token;
create table oauth_refresh_token (
  token_id VARCHAR(256),
  token BLOB,
  authentication BLOB
);

drop table if exists oauth_code;
create table oauth_code (
  code VARCHAR(256), authentication BLOB
);

drop table if exists oauth_approvals;
create table oauth_approvals (
   userId VARCHAR(256),
   clientId VARCHAR(256),
   scope VARCHAR(256),
   status VARCHAR(10),
   expiresAt TIMESTAMP,
   lastModifiedAt TIMESTAMP
);


-- customized oauth_client_details table
drop table if exists ClientDetails;
create table ClientDetails (
  appId VARCHAR(256) PRIMARY KEY,
  resourceIds VARCHAR(256),
  appSecret VARCHAR(256),
  scope VARCHAR(256),
  grantTypes VARCHAR(256),
  redirectUrl VARCHAR(256),
  authorities VARCHAR(256),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additionalInformation VARCHAR(4096),
  autoApproveScopes VARCHAR(256)
);

-- 插入数据
INSERT INTO `oauth_client_details` VALUES ('sysclientid', NULL, '$2a$10$tFPqxfJVvGSR/gVm5KDUJeRGswbiE9HSVNMO8NcG5rni5a8fmG6qm', 'UserInfo', 'authorization_code', 'http://www.baidu.com', NULL, NULL, NULL, NULL, NULL)

      VI. 启动程序,访问授权码接口获取授权码,然后根据授权码访问Token接口请求Token,可参考上面【内存保存访问令牌】步骤。访问的clientId和secretId信息存储在oauth_client_details表中,其中的secretId信息是经过BCryptPasswordEncoder加密的,可自定插入数据进行测试。
2. 资源服务器
 a 配置资源服务

/**
 * 配置此服务为资源服务器
 */
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    
}

 b. 配置提供的资源,即相应的Http服务,这里可使用注解@PreAuthorize对方法级别进行权限校验。之后使用Token进行访问时会校验Token对应用户的权限。

@RestController()
@RequestMapping("/api")
public class HelloController {

    @GetMapping("/")
    public String hello() {
        return "Hello, I am /";
    }

    @GetMapping("/hello")
    @PreAuthorize("hasRole('ROLE_SYSTEM')")
    public String getHello(String access_token) {
        return "Hello, I am /hello";
    }
}

 c. 配置认证服务器配置

# 认证服务器配置
security:
  oauth2:
    client:
      client-id: sysclientid
      client-secret: syssecretid    #加密前的
      access-token-uri: http://localhost:8081/oauth/token
      user-authorization-uri: http://localhost:8081/oauth/authorize
    resource:
      token-info-uri: http://localhost:8081/oauth/check_token

 d. 进行Http资源访问,请求是需携带参数Token信息,可直接在请求地址后携带 ?access_token= 参数,也可放到Header中携带。
      I. 使用直接地址携带参数格式例如:

http://localhost:8080/api/?access_token=3cd5e11f-76b1-4ae6-8fb6-38e67da29452
http://localhost:8080/api/hello?access_token=3cd5e11f-76b1-4ae6-8fb6-38e67da29452

            使用Header携带参数格式如下,注意Bearer后有空格分隔!
在这里插入图片描述在这里插入图片描述

      II. 下面使用使用Postman工具演示各种场景:不使用token或者错误token直接访问,结果如下:
在这里插入图片描述
      III. 使用正确token访问,token获取参考上面基于内存或者基于数据库存储令牌,结果如下:
在这里插入图片描述
      IV. 访问权限不够的资源,会返回不允许访问信息,结果如下:
在这里插入图片描述

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值