什么是 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. CF1254D Tree Queries(树链剖分)

    出题人的做法是 \(O(n\sqrt{n\log n})\),结果这场结束后就被狂喷,一群人给出了 \(O(n\sqrt{n})\) 做法,甚至 \(O(n\log n)\) 都出来了-- 首先发现, ...

  2. 【MySQL】MySQL 8.0的SYS视图

    MySQL的SYS视图 MySQL8.0的发展越来越趋同与Oracle,为了更好的监控MySQL的一些相关指标,出现了SYS视图,用于监控. 1.MySQL版本 (root@localhost) [s ...

  3. 跟着高淇学Python——第一到第三章总结

    2019/10/26 第一章:Python介绍 Python是一种解释型,面向对象的语言.特点是: 可读性强 简洁,简洁 面向对象 免费开源 可移植性和跨平台性 丰富的库 可扩展性 应用范围:1.人工 ...

  4. Sql 代码规范说明

    对于程序工作者来说,代码的阅读必不可少,好的代码让人读起来一目了然.神清气爽,做代码调试也可以很开的捋顺逻辑定位问题,但是如果遇到一些可读性较差,毫无规矩可言的代码,那真的比吃了翔都难受啊,如果再让你 ...

  5. 零基础学python,python视频教程

    零基础学python,python视频教程 这是我收集到的互联网上的视频资源,所有内容均来自互联网.仅供学习使用. 目前我在也在学习过程中,会把学习过程中遇到问题以及解决问题的方式,总结到我的公众号[ ...

  6. MySQL中的group_concat函数的使用

    本文通过实例介绍了MySQL中的group_concat函数的使用方法,比如select group_concat(name) . MySQL中group_concat函数 完整的语法如下: grou ...

  7. 制作windows安装包的工具

    https://nsis.sourceforge.io/Download https://www.installaware.com/ https://www.advancedinstaller.com ...

  8. LooseVersion()使用及.__version__版本号的获取

    我简单看了distutils库,但发现目前还用不到,感觉有些复杂.因此我简单复制了别人的介绍,如下: Distutils可以用来在Python环境中构建和安装额外的模块.新的模块可以是纯Python的 ...

  9. Oracle 中Number的长度定义

    Number可以通过如下格式来指定:Field_NAME Number(precision ,scale),其中precision指Number可以存储的最大数字长度(不包括左右两边的0),scale ...

  10. plus.webview.create 使用方法

    plus.webview.create( "xxx.html", //打开页面地址 "xxx", //打开页面id值 { //窗口样式 width: '100% ...