什么是 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. linux下用sox音频处理常用方法

    一 sox可以给pcm文件加头 方法:sox -t raw -c 1 -e signed-integer -b 16 -r 16000 test.pcm test.wav 二 修改采样率: 方法: s ...

  2. Saiku默认给数据类型的数据添加小数点问题处理(三十一)

    Saiku默认给数据类型的数据添加小数点问题处理 不知道大家有没有遇到过saiku定义的维度信息,数据类型时 展示出来的数据会自动加上 .0的后缀. 比如我定义了一个维度为 年, 在数据库中为 int ...

  3. PELT算法

    参考:http://www.wowotech.net/process_management/PELT.html 本文是对https://lwn.net/Articles/531853/的翻译 mark ...

  4. jieba 分词使用入门

    1. 介绍 JIEBA 是目前最好的 Python 中文分词组件,它主要有以下 3 种特性: 支持 3 种分词模式:精确模式.全模式.搜索引擎模式 支持繁体分词 支持自定义词典 import jieb ...

  5. WEB-INF文件夹作用

    WEB-INF是Java的WEB应用的安全目录,客户端无法访问,只能通过服务端访问,从而实现了代码的安全.在WEB-INF中主要是系统运行的配置信息和环境 主要有classes.config.lib文 ...

  6. ASP.NET Core基于K8S的微服务电商案例实践--学习笔记

    摘要 一个完整的电商项目微服务的实践过程,从选型.业务设计.架构设计到开发过程管理.以及上线运维的完整过程总结与剖析. 讲师介绍 产品需求介绍 纯线上商城 线上线下一体化 跨行业 跨商业模式 从0开始 ...

  7. 练手WPF(三)——扫雷小游戏的简易实现(下)

    十四.响应鼠标点击事件    (1)设置对应坐标位置为相应的前景状态 /// <summary> /// 设置单元格图样 /// </summary> /// <para ...

  8. go-gtk环境配置

    go-gtk环境配置(用于go的图形化界面) 弄了一天,终于把这个环境给搭建好了,差不多就是安装msys2->安装gtk的各个版本->安装mingw(我原来装有但不影响,因为重复了,环境变 ...

  9. int[]里数的个数怎么由输入决定?-----动态数组。

    java中如何创建动态数组?(摘自百度知道) Java动态数组是一种可以任意伸缩数组长度的对象,在Java中比较常用的是ArrayList,ArrayList是javaAPI中自带的java.util ...

  10. 环信即时通讯在工程中的安装——Nusen_Liu

    即时通讯-环信 准备 1.下载SDK http://www.easemob.com/download 2.证书下载上传 后期发送消息 需要推送发送的内容 http://docs.easemob.com ...