Spring boot --- Spring Security(二)

   文章部分图片来自参考资料,这篇文章主要讲 spring security  oauth

概述

上一篇我们学习了 SS 中重要的工作原理和几个大概的认证和授权过程。而 spring security oauth 用到的就是 spring security 知识,我们学习 sso 之前先看一下oauth 是什么,可以学习阮一峰老师的文章

oauth 的流程图如下 : (牢牢记住这张图)

主要的角色有资源持有者,资源服务器,认证服务器,还有用户

授权(获取 Access Token)的方式有多种方式

  • 授权码
  • 简化模式
  • 客户端模式
  • 密码模式

oauth 可以理解成工作中,你(Client)去出差,回来需要报销,会计(Authorzation Server)首先需要你请示老板(Resource Owned)是否同意给你报销出差费用,假如同意了,你就回来找会计,把老板的凭证给她,她会给你一个token (获取token过程的方式有多种,就是前面提到的), 然后你带着 token 再去财务(Resource Server)领钱 ,结束流程。

Spring Security Oauth

    学习 Spring Security Oauth  ,先学习一个例子(出处),然后根据例子配合oauth 流程学习

我们按照上面的例子敲完代码后,整个流程走完再结合oauth 授权的流程

例子中使用的授权码,而获取Access Token ,为何先给授权码,而不直接给 Access Token 呢 ?

给授权码,再用授权码去获取Access Token 的原因是授权码可以让服务端知道client 的身份。

spring security oauth 角色

oauth 获取中几个重要的角色中在 spring security oauth 中对应的有 :

  • @EnableResourceServer
  • @EnableAuthorazaitonServer
  • @EnableOauthClient

Resource Owned 的角色放在 Authorazation Server,就是代码中的 UserDetail

UserDetail

UserDetail 的作用是用来认证即是上面oauth 流程图的A 步骤,示例如下 :

@Service
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private UserService userService;

    /**
     * 授权的时候是对角色授权,而认证的时候应该基于资源,而不是角色,因为资源是不变的,而用户的角色是会变的
     */

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser sysUser = userService.getUserByName(username);
        if (null == sysUser) {
            throw new UsernameNotFoundException(username);
        }
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for (SysRole role : sysUser.getRoleList()) {
            for (SysPermission permission : role.getPermissionList()) {
                authorities.add(new SimpleGrantedAuthority(permission.getCode()));
            }
        }

        return new User(sysUser.getUsername(), sysUser.getPassword(), authorities);
    }
}

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    ...

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

} 

TokenStore

获取token,那么很明显需要一个储存token 的容器,例如我们想使用 Redis 来存储token ,当我们需要实现自己token 存储容器时可以如下使用 :

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    ... 

    /**
     * 该方法是用来配置Authorization Server endpoints的一些非安全特性的,比如token存储、token自定义、授权类型等等的
     * 默认情况下,你不需要做任何事情,除非你需要密码授权,那么在这种情况下你需要提供一个AuthenticationManager
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)
                .tokenStore(new MyRedisTokenStore(redisConnectionFactory));
    }

} 

@Service
public class MyRedisTokenStore implements TokenStore {
   .... 

}

clientDetail

使用授权码方式获取 Access Token 时先发放授权码,而是否可以发送授权码需要验证client 的身份,clientDetail 便是便是表述 client 基本信息的类

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    ...

    @Resource
    private DataSource dataSource;

    /**
     * 配置ClientDetailsService
     * 注意,除非你在下面的configure(AuthorizationServerEndpointsConfigurer)中指定了一个AuthenticationManager,否则密码授权方式不可用。
     * 至少配置一个client,否则服务器将不会启动。
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource);
    }

}

token 和 clientDetail 基类表述

可以参考此处构建自己的 token 和 clientDetail ,以下是数据库实现

-- used in tests that use HSQL
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)
);

create table oauth_client_token (
    token_id VARCHAR(256),
    token LONGVARBINARY,
    authentication_id VARCHAR(256) PRIMARY KEY,
    user_name VARCHAR(256),
    client_id VARCHAR(256)
);

create table oauth_access_token (
    token_id VARCHAR(256),
    token LONGVARBINARY,
    authentication_id VARCHAR(256) PRIMARY KEY,
    user_name VARCHAR(256),
    client_id VARCHAR(256),
    authentication LONGVARBINARY,
    refresh_token VARCHAR(256)
);

create table oauth_refresh_token (
    token_id VARCHAR(256),
    token LONGVARBINARY,
    authentication LONGVARBINARY
);

create table oauth_code (
    code VARCHAR(256), authentication LONGVARBINARY
);

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

完整服务端配置

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailsService myUserDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/oauth/**","/login/**", "/logout").permitAll()
                .anyRequest().authenticated()   // 其他地址的访问均需验证权限
                .and()
                .formLogin()
                .loginPage("/login")
                .and()
                .logout().logoutUrl("/logout").logoutSuccessUrl("/login");
    }

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

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

    @Bean
    @Override
    public AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Resource
    private DataSource dataSource;

    @Autowired
    RedisConnectionFactory redisConnectionFactory;

    @Autowired
    private AuthenticationManager authenticationManager;

    /**
     * 配置授权服务器的安全,意味着实际上是/oauth/token端点。
     * /oauth/authorize端点也应该是安全的
     * 默认的设置覆盖到了绝大多数需求,所以一般情况下你不需要做任何事情。
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        super.configure(security);
    }

    /**
     * 配置ClientDetailsService
     * 注意,除非你在下面的configure(AuthorizationServerEndpointsConfigurer)中指定了一个AuthenticationManager,否则密码授权方式不可用。
     * 至少配置一个client,否则服务器将不会启动。
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource);
    }

    /**
     * 该方法是用来配置Authorization Server endpoints的一些非安全特性的,比如token存储、token自定义、授权类型等等的
     * 默认情况下,你不需要做任何事情,除非你需要密码授权,那么在这种情况下你需要提供一个AuthenticationManager
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager)
                .tokenStore(new MyRedisTokenStore(redisConnectionFactory));
    }
}

当资源服务器和认证服务器是同一个服务器的时候  :

Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailsService myUserDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/oauth/**","/login/**", "/logout").permitAll()
                .anyRequest().authenticated()   // 其他地址的访问均需验证权限
                .and()
                .formLogin()
                .loginPage("/login")
                .and()
                .logout().logoutUrl("/logout").logoutSuccessUrl("/login");
    }

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

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

    @Bean
    @Override
    public AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

工作流程

http://blog.didispace.com/xjf-spring-security-1/

http://blog.didispace.com/xjf-spring-security-1/

总结

文章并不是 spring oauth 的入门篇,主要是结合 oauth 的流程图找到对应 spring security oauth 框架中的逻辑对应,更好地自定义改造。结合下面的参考资料,可以完成单点登录的功能。

参考资料

原文地址:https://www.cnblogs.com/Benjious/p/10638913.html

时间: 2024-10-05 20:31:07

Spring boot --- Spring Security(二)的相关文章

256.Spring Boot+Spring Security: MD5是加密算法吗?

说明 (1)JDK版本:1.8 (2)Spring Boot 2.0.6 (3)Spring Security 5.0.9 (4)Spring Data JPA 2.0.11.RELEASE (5)hibernate5.2.17.Final (6)MySQLDriver 5.1.47 (7)MySQL 8.0.12 前言 有网友在公众号留言:准确的说md5是摘要算法不是加密算法 针对这个问题,当时也没有仔细的思考,空下来的时候,对于这个问题整理了下思路. 一.加密算法 1.1 加密和解密 1.1

Spring boot +Spring Security + Thymeleaf 认证失败返回错误信息

spring boot以其众多友谊的特性,如零配置.微服务等,吸引了很多的粉丝.而其与Spring Security安全框架的无缝结合,使其具备的安全的特性.在此基础上使用Thymeleaf模板引擎进行渲染,静动态结合,让页面开发更加简单.直观. 通过表单提交登录的用户名和密码是登录接口比较常见的一种设计.在初学的过程中,我也不例外的采用个这种方式.表单设计见下图. 登录成功,完成正常的主页面跳转,这个不存在问题.存在问题的是,登录失败了该咋办呢?我就在考虑,由于thymeleaf的局部刷新操作

Spring Boot学习记录(二)--thymeleaf模板

Spring Boot学习记录(二)–thymeleaf模板 标签(空格分隔): spring-boot 自从来公司后都没用过jsp当界面渲染了,因为前后端分离不是很好,反而模板引擎用的比较多,thymeleaf最大的优势后缀为html,就是只需要浏览器就可以展现页面了,还有就是thymeleaf可以很好的和spring集成.下面开始学习. 1.引入依赖 maven中直接引入 <dependency> <groupId>org.springframework.boot</gr

Angular集成Spring Boot,Spring Security,JWT和CORS

本文介绍了Spring Boot的基本配置,Angular集成Spring Boot.Spring Security的方法.当前流行的JWT更适合与Angular集成,优于Spring Secuity提供的CSRF.另外引入了springfox-swagger和spring-boot-starter-actuator,演示了如何利用Swagger生成JSON API文档,如何利用Actuator监控应用. 本文前端基于Angular官方样例Tour of Heroes,请先到官网下载. 技术堆栈

Spring Boot 启动(二) 配置详解

Spring Boot 启动(二) 配置详解 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Spring Boot 配置文件加载顺序 Spring Boot 配置文件加载分析 - ConfigFileApplicationListener 一.Spring Framework 配置 略... 二.Spring Boot 配置 2.1 随机数配置 name.value=${random.int} name.int=${

255.Spring Boot+Spring Security:使用md5加密

说明 (1)JDK版本:1.8 (2)Spring Boot 2.0.6 (3)Spring Security 5.0.9 (4)Spring Data JPA 2.0.11.RELEASE (5)hibernate5.2.17.Final (6)MySQLDriver 5.1.47 (7)MySQL 8.0.12 需求缘起 很多时候,我们自己已经有现成的一套系统在运行了,这时候要接入spring security的话,那么难免会碰到一个问题:就是自己设计的密码加密方式和spring secur

Spring Boot/Spring Cloud、ESB、Dubbo

如何使用Spring Boot/Spring Cloud 实现微服务应用spring Cloud是一个基于Spring Boot实现的云应用开发工具,它为基于JVM的云应用开发中的配置管理.服务发现.断路器.智能路由.微代理.控制总线.全局锁.决策竞选.分布式会话和集群状态管理等操作提供了一种简单的开发方式. Spring Cloud与Dubbo对比提到Dubbo,我想顺便提下ESB,目前央视新华社也在用ESB来做任务编排,这里先比较下Dubbo和ESB: ESB(企业数据总线),一般采用集中式

Spring -&gt; Spring Boot &gt; Spring Cloud

Spring -> Spring Boot > Spring Cloud 这几天刚刚上班,公司用的是Spring Cloud,接触不多.我得赶快学起来. 想学习就必须得知道什么是微服务,什么是Spring Boot,什么是Spring Cloud,以及两者之间有什么关系? 什么是微服务? 简而言之,微服务架构风格是一种将单个应用程序作为一套小型服务开发的方法,每种应用程序都在自己的进程中运行,并与轻量级机制(通常是HTTP资源API)进行通信. 这些服务是围绕业务功能构建的,可以通过全自动部署

215.Spring Boot+Spring Security:初体验

[视频&交流平台] SpringBoot视频:http://t.cn/R3QepWG Spring Cloud视频:http://t.cn/R3QeRZc SpringBoot Shiro视频:http://t.cn/R3QDMbh SpringBoot交流平台:http://t.cn/R3QDhU0 SpringData和JPA视频:http://t.cn/R1pSojf SpringSecurity5.0视频:http://t.cn/EwlLjHh 说明 (1)JDK版本:1.8 (2)Sp

How to use JDBC-Authentication of Spring Boot/Spring Security with Flyway

java.lang.IllegalStateException: Failed to load ApplicationContext at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124) at org.springframework.test.context.suppo