什么是 oAuth

oAuth 协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是 oAuth 的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此 oAuth 是安全的。

什么是 Spring Security

Spring Security 是一个安全框架,前身是 Acegi Security,能够为 Spring 企业应用系统提供声明式的安全访问控制。Spring Security 基于 Servlet 过滤器、IOC 和 AOP,为 Web 请求和方法调用提供身份确认和授权处理,避免了代码耦合,减少了大量重复代码工作。

客户端授权模式

客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。oAuth 2.0 定义了四种授权方式。

  • implicit:简化模式,不推荐使用
  • authorization code:授权码模式(本文章采用这种模式)
  • resource owner password credentials:密码模式
  • client credentials:客户端模式

个人对oAuth2认证过程的理解

编码部分

  1. 创建项目工程,本文章采用多module方式
    主要是配置一个pom,文章结束后代码会发到github,这里不复制了
  2. 创建统一依赖
    统一依赖中配置org.springframework.cloud
    springboot2.1.x需要使用Greenwich版本
  3. 创建认证服务器模块(内存模拟版本)
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    // 注入 WebSecurityConfiguration 中配置的 BCryptPasswordEncoder
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 配置客户端
        clients
                // 使用内存设置
                .inMemory()
                // client_id
                .withClient("client")
                // client_secret
                .secret(passwordEncoder.encode("secret"))
                // 授权类型
                .authorizedGrantTypes("authorization_code")
                // 授权范围
                .scopes("app")
                // 注册回调地址
                .redirectUris("http://blog.yhhu.xyz");
    }
}
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        // 设置默认的加密方式
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        auth.inMemoryAuthentication()
                // 在内存中创建用户并为密码加密
                .withUser("user").password(passwordEncoder().encode("123456")).roles("USER")
                .and()
                .withUser("admin").password(passwordEncoder().encode("123456")).roles("ADMIN");
    }
}

SpringSecurity提供默认的登录界面,并且提供RestfulAPI

spring:
  application:
    name: oauth2-server
server:
  port: 8080

==必须遵循这样的请求url==
获取code:在浏览器直接访问http://localhost:8080/oauth/authorize?client_id=client&response_type=code
通过code获取access_token:http://client:secret@localhost:8080/oauth/token

这里需要带两个参数,采用x-www-form-urlencoded方式,grant_type为authorization_code,code为上一步获取到的内容
url说明:client:secret不是固定的,这取决于你在AuthorizationServerConfiguration中withClient和secret的设置

  1. 创建认证服务器模块(JDBC版本)

    1. 创建一个oauth数据库,官方给的表示H2的,mysql有点改动,百度找了一个
    CREATE TABLE `clientdetails` (
      `appId` varchar(128) NOT NULL,
      `resourceIds` varchar(256) DEFAULT NULL,
      `appSecret` varchar(256) DEFAULT NULL,
      `scope` varchar(256) DEFAULT NULL,
      `grantTypes` varchar(256) DEFAULT NULL,
      `redirectUrl` varchar(256) DEFAULT NULL,
      `authorities` varchar(256) DEFAULT NULL,
      `access_token_validity` int(11) DEFAULT NULL,
      `refresh_token_validity` int(11) DEFAULT NULL,
      `additionalInformation` varchar(4096) DEFAULT NULL,
      `autoApproveScopes` varchar(256) DEFAULT NULL,
      PRIMARY KEY (`appId`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    CREATE TABLE `oauth_access_token` (
      `token_id` varchar(256) DEFAULT NULL,
      `token` blob,
      `authentication_id` varchar(128) NOT NULL,
      `user_name` varchar(256) DEFAULT NULL,
      `client_id` varchar(256) DEFAULT NULL,
      `authentication` blob,
      `refresh_token` varchar(256) DEFAULT NULL,
      PRIMARY KEY (`authentication_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    CREATE TABLE `oauth_approvals` (
      `userId` varchar(256) DEFAULT NULL,
      `clientId` varchar(256) DEFAULT NULL,
      `scope` varchar(256) DEFAULT NULL,
      `status` varchar(10) DEFAULT NULL,
      `expiresAt` timestamp NULL DEFAULT NULL,
      `lastModifiedAt` timestamp NULL DEFAULT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    CREATE TABLE `oauth_client_details` (
      `client_id` varchar(128) NOT NULL,
      `resource_ids` varchar(256) DEFAULT NULL,
      `client_secret` varchar(256) DEFAULT NULL,
      `scope` varchar(256) DEFAULT NULL,
      `authorized_grant_types` varchar(256) DEFAULT NULL,
      `web_server_redirect_uri` varchar(256) DEFAULT NULL,
      `authorities` varchar(256) DEFAULT NULL,
      `access_token_validity` int(11) DEFAULT NULL,
      `refresh_token_validity` int(11) DEFAULT NULL,
      `additional_information` varchar(4096) DEFAULT NULL,
      `autoapprove` varchar(256) DEFAULT NULL,
      PRIMARY KEY (`client_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    CREATE TABLE `oauth_client_token` (
      `token_id` varchar(256) DEFAULT NULL,
      `token` blob,
      `authentication_id` varchar(128) NOT NULL,
      `user_name` varchar(256) DEFAULT NULL,
      `client_id` varchar(256) DEFAULT NULL,
      PRIMARY KEY (`authentication_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    CREATE TABLE `oauth_code` (
      `code` varchar(256) DEFAULT NULL,
      `authentication` blob
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    CREATE TABLE `oauth_refresh_token` (
      `token_id` varchar(256) DEFAULT NULL,
      `token` blob,
      `authentication` blob
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    1. 操作一下oauth_client_details表
      需要设置的内容为client_id,client_secret,scope,authorized_grant_types,web_server_redirect_uri,注意client_secret是经过加密的

至此完成验证功能,其他操作和内存版都是一样的,完成后可以在数据库中看到看到token相关信息

RBAC(Role-Based Access Control,基于角色的访问控制)

增加了几个表,模型如下

可以做的简单一点,可是这样做功能更加完备,单单在user表中插入role列的话不便于管理

下面开始具体实现

  1. 在认证服务器端排除token检查的权限判断
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/oauth/check_token");
    }
  1. 创建一个service,查询用户使用(代码略)

  2. 将授权模式由内存模式改为userDetailService方式

    @Autowired
    private DataSource dataSource;

    @Bean
    public TokenStore tokenStore() {
        // 基于 JDBC 实现,令牌保存到数据
        return new JdbcTokenStore(dataSource);
    }

    @Bean
    public ClientDetailsService jdbcClientDetails() {
        // 基于 JDBC 实现,需要事先在数据库配置客户端信息
        return new JdbcClientDetailsService(dataSource);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception
    {
        // 设置令牌
        endpoints.tokenStore(tokenStore());
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService());
    }
  1. 认证中心UserDetail权限判断逻辑
public class UserDetailServiceImpl implements UserDetailsService {
    @Autowired
    private TbUserService tbUserService;
    @Autowired
    private TbPermissionService tbPermissionService;
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        TbUser tbUser = tbUserService.getByUsername(s);
        List<GrantedAuthority> grantedAuthorities= Lists.newArrayList();
        if (tbUser != null) {
            List<TbPermission> tbPermissions = tbPermissionService.selectByUserId(tbUser.getId());
            tbPermissions.forEach(item -> {
                GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(item.getEnname());
                grantedAuthorities.add(grantedAuthority);
            });
            return new User(tbUser.getUsername(),tbUser.getPassword(),grantedAuthorities);
        }
        return null;
    }
}
  1. 如何自定义一个权限控制方法?(通过permission中url对应的方式来判断权限)

定义权限判断类

@Component("permission")
public class PermissionCheck {
    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
        boolean hasPermission = false;
    //取出当前token对应用户所拥有的权限
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        for (GrantedAuthority authority : authorities) {
            if (antPathMatcher.match(authority.getAuthority(), request.getRequestURI())) {
                hasPermission = true;
                break;
            }
        }
        return hasPermission;
    }
}

在资源服务器中配置access("")方法来调用hasPermission方法

public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.exceptionHandling()
                .and()
                //管理session的创建策略永远不会创建HttpSession,它不会使用HttpSession来获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                //ALWAYS总是创建HttpSession
                //IF_REQUIREDSpring Security只会在需要时创建一个HttpSession
                //NEVERSpring Security不会创建HttpSession,但如果它已经存在,将可以使用HttpSession
                .and()
                .authorizeRequests()
                .anyRequest().access("@permission.hasPermission(request,authentication)");
        // 以下为配置所需保护的资源路径及权限,需要与认证服务器配置的授权部分对应
//                .antMatchers("/**").hasAuthority("SystemContent");
    }
}

补充一个错误:Spring security oauth2 throwing "no bean resolver registered" for custom bean

在ResourceServerConfiguration类下补充以下配置

    /**
     * 以下为采坑地方,如果不使用自己的动态权限控制,下面无需配置也能运行
     * 原因暂时不明,解决方案为参考以下issues地址
     * https://github.com/spring-projects/spring-security-oauth/issues/730#issuecomment-219480394
     */
    @Autowired
    private OAuth2WebSecurityExpressionHandler expressionHandler;

    @Bean
    public OAuth2WebSecurityExpressionHandler oAuth2WebSecurityExpressionHandler(ApplicationContext applicationContext) {
        OAuth2WebSecurityExpressionHandler expressionHandler = new OAuth2WebSecurityExpressionHandler();
        expressionHandler.setApplicationContext(applicationContext);
        return expressionHandler;
    }

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

完整代码地址

github:oauth2

Spring Security OAuth2学习的更多相关文章

  1. Spring Cloud 学习 (十) Spring Security, OAuth2, JWT

    通过 Spring Security + OAuth2 认证和鉴权,每次请求都需要经过 OAuth Server 验证当前 token 的合法性,并且需要查询该 token 对应的用户权限,在高并发场 ...

  2. 【OAuth2.0】Spring Security OAuth2.0篇之初识

    不吐不快 因为项目需求开始接触OAuth2.0授权协议.断断续续接触了有两周左右的时间.不得不吐槽的,依然是自己的学习习惯问题,总是着急想了解一切,习惯性地钻牛角尖去理解小的细节,而不是从宏观上去掌握 ...

  3. Spring Security Oauth2系列(一)

    前言: 关于oauth2,其实是一个规范,本文重点讲解spring对他进行的实现,如果你还不清楚授权服务器,资源服务器,认证授权等基础概念,可以移步理解OAuth 2.0 - 阮一峰,这是一篇对于oa ...

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

    最近再学习spring security oauth2.下载了官方的例子sparklr2和tonr2进行学习.但是例子里包含的东西太多,不知道最简单最主要的配置有哪些.所以决定自己尝试搭建简单版本的例 ...

  5. Spring Security OAuth2 SSO

    通常公司肯定不止一个系统,每个系统都需要进行认证和权限控制,不可能每个每个系统都自己去写,这个时候需要把登录单独提出来 登录和授权是统一的 业务系统该怎么写还怎么写 最近学习了一下Spring Sec ...

  6. Spring Security Oauth2 的配置

    使用oauth2保护你的应用,可以分为简易的分为三个步骤 配置资源服务器 配置认证服务器 配置spring security 前两点是oauth2的主体内容,但前面我已经描述过了,spring sec ...

  7. Re:从零开始的Spring Security Oauth2(一)

    前言 今天来聊聊一个接口对接的场景,A厂家有一套HTTP接口需要提供给B厂家使用,由于是外网环境,所以需要有一套安全机制保障,这个时候oauth2就可以作为一个方案. 关于oauth2,其实是一个规范 ...

  8. Spring Security OAuth2 Demo —— 授权码模式

    本文可以转载,但请注明出处https://www.cnblogs.com/hellxz/p/oauth2_oauthcode_pattern.html 写在前边 在文章OAuth 2.0 概念及授权流 ...

  9. Spring Security 解析(七) —— Spring Security Oauth2 源码解析

    Spring Security 解析(七) -- Spring Security Oauth2 源码解析   在学习Spring Cloud 时,遇到了授权服务oauth 相关内容时,总是一知半解,因 ...

随机推荐

  1. Educational Codeforces Round 76 (Rated for Div. 2) B. Magic Stick 水题

    B. Magic Stick Recently Petya walked in the forest and found a magic stick. Since Petya really likes ...

  2. PKUWC/SC 做题笔记

    去年不知道干了些啥,什么省选/营题都没做. 现在赶应该还来得及(?) 「PKUWC2018」Minimax Done 2019.12.04 9:38:55 线段树合并船新玩法??? \(O(n^2)\ ...

  3. Codeforces Round 596 题解

    万幸的是终于碰上了一场上分好场. 不幸的是一开始差点不会 A. 万幸的是想了个不那么稳的结论过了 pretest. 不幸的是罚时很高,而且慌得一比. 万幸的是然后半个小时内把 B 和 C 码了. 不幸 ...

  4. Paper | Deep Mutual Learning

    目录 1. 动机详述和方法简介 2. 相关工作 3. 方法 3.1 Formulation 3.2 实现 3.3 弱监督学习 4. 实验 4.1 基本实验 4.2 深入实验 [算法和公式很simple ...

  5. Python进阶小结

    目录 一.异常TODO 二.深浅拷贝 2.1 拷贝 2.2 浅拷贝 2.3 深拷贝 三.数据类型内置方法 3.1 数字类型内置方法 3.1.1 整型 3.1.2 浮点型 3.2 字符串类型内置方法 3 ...

  6. Windows下cwrsync客户端与rsync群辉存储服务端定时数据同步

    cwRsync简介 cwRsync是Rsync在Windows上的实现版本,Rsync通过使用特定算法的文件传输技术,可以在网络上传输只修改了的文件. cwRsync主要用于Windows上的远程文件 ...

  7. JDBC释放数据库连接

    try(){}写法会自动关闭连接 String sql = "select password from user where name = ?"; try(Connection c ...

  8. SpringBoot开发案例之mail中文附件名字乱码

    最近在开发一个邮件发送多附件的微服务,使用的是org.springframework.mail.javamail.JavaMailSender;包下面的JavaMailSender 但是发送出来的附件 ...

  9. [IDA] 将连续的单个变量值修改为结构体

    直接选中最上边的结构体,使用ALT+Q来进行修改.

  10. kafka的主题与消费

    同一个消费者组不能同时消费同一个分区的数据 不同分区可以消费同一组不同消费者 同一个消费者可以同时消费多个topicA的数据 Topic和consumer依赖zookeeper,producer不依赖