Spring boot 学习 ---- Spring Security

Spring Security

理论

认证

认证是指判断用户的身份。比如用户名密码登录,二维码登录,指纹认证等。。。

会话

为了避免用户每次都需要认证,用户的信息保存在会话中。常用的有基于session 的认证方式,

基于token的认证方式。

授权

授权是为了限制用户对资源的使用。授权需要用户先认证通过后再进行授权,用户拥有资源的访问则正常访问,没有权限则拒绝访问。

授权的数据模型

用户对哪些资源进行哪些操作。

who -> what -> how

资源:资源分为两类。

  • 功能资源:系统中的菜单,按钮,代码方法等。对于web而言,每个功能资源对于一个URL

  • 数据资源:数据资源由资源类型和资源实例组成。

    • 资源类型:一张表就是一个资源类型
    • 资源实例:表中的具体某一行数据为资源实例

授权我们至少需要创建5张表:

用户 角色 权限

用户角色关系表 角色权限关系表

其中角色只是为了方便给用户授权。

最终目的是给每个用户赋予权限。

6表则是如下关系:

用户 角色 资源 权限

用户角色关系表 角色权限关系表

RBAC

  • 基于角色的访问(需要修改代码)

  • 基于资源的访问(推荐)

Spring Boot 中使用

Spring Security 认证

SpringSecurity默认提供认证页面

maven 依赖

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

认证配置

  • 定义用户信息服务
   @Bean
    public UserDetailsService userDetailsService(){
       //   基于内存的认证
       InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
           	manager.createUser(User.withUsername("zhangsan").password("123").authorities("ADMIN").build());
       manager.createUser(User.withUsername("lizi").password("456").authorities("USER").build());
       return manager;
   }
  • 密码编码器
   @Bean
    public PasswordEncoder passwordEncoder(){
       //   无加密(通常不用这个)
       // return NoOpPasswordEncoder.getInstance();
        
        //   BCryp加密
        return new BCryptPasswordEncoder();
   }
  • 安全拦截机制(相当于拦截器)
@Override
    protected void configure(HttpSecurity http) throws Exception {
               http.authorizeRequests()     // 对请求进行验证
                .antMatchers("/role/**").permitAll()		//  开放授权,不拦截
                .antMatchers("/admin/**").hasAuthority("ADMIN")     // 必须有ADMIN权限
                .antMatchers("/user/**").hasAnyAuthority("USER", "ADMIN")       //有任意一种权限	(当然这里也可以使用角色控制资源访问但是不推荐)
                .anyRequest()     //任意请求(这里主要指方法)
                .authenticated()   //// 需要身份认证
                .and()   //表示一个配置的结束
                .formLogin().permitAll()  //开启SpringSecurity内置的表单登录,会提供一个/login接口
                .and()
                .logout().permitAll()  //开启SpringSecurity内置的退出登录,会为我们提供一个/logout接口
                .and()
                .csrf().disable();    //关闭csrf跨站伪造请求
    }

认证流程

修改为数据库认证配置

之前的基于内存的认证 去除 即 去除之前的UserDetailsService 配置

@Service
public class UserDetailService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;
    /**
     * 根据账号查询用户信息
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 从数据获取账号信息
        UserDto userDto = userRepository.getUserByUserName(username);
        if (userDto == null) {
            return null;
        }
        UserDetails userDetails = User.withUsername(userDto.getUsername())
                .password(userDto.getPassword()).authorities("ADMIN").build();
        return userDetails;
    }
}

Security 授权流程

底层原理使用投票的形式。

Spring Security会话

获取用户身份

    @GetMapping("/info")
    public String info(){
        String username = null;
        //  当前认证通过的用户身份
        SecurityContext context = SecurityContextHolder.getContext();
        Authentication authentication = context.getAuthentication();
        Object principal = authentication.getPrincipal();
        if (principal != null) {
            username = "匿名";
        }
        if (principal instanceof UserDetails) {
            username = ((UserDetails) principal).getUsername();
        }
        return username;
    }

控制会话

我们可以通过一下选项准确控制会话何时创建以与spring security 的交互方式

机制 描述
always 如果没有session存在就创建一个
ifRequired 如果需要就创建一个Session(默认) 登录时
never Spring Security将不会创建session,但如果应用中其他地方创建了session,那么Spring Security 将会使用它
stateless Spring Security 将绝对不会 创建session,也不使用session

配置方式:

在Spring Security 的WebSecurityConfigurerAdapter

 @Override
    protected void configure(HttpSecurity http) throws Exception {
  http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
    }

自定义页面

自定义login页面

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //  自定义登录
        http.formLogin().loginPage("/login-view")
                .loginProcessingUrl("/login")
                .successForwardUrl("/login-success")
                .permitAll();
    }

自定义logout页面

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.logout().logoutUrl("/logout").logoutSuccessUrl("/login-view?logout=true");
        //  配置session
    }

Spring Security 授权

  • 建表SQL
CREATE DATABASE /*!32312 IF NOT EXISTS*/`security` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;

USE `security`;

/*Table structure for table `t_permission` */

DROP TABLE IF EXISTS `t_permission`;

CREATE TABLE `t_permission` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `code` varchar(255) DEFAULT NULL,
  `description` varchar(255) DEFAULT NULL,
  `url` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

/*Table structure for table `t_role` */

DROP TABLE IF EXISTS `t_role`;

CREATE TABLE `t_role` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `role_name` varchar(255) DEFAULT NULL,
  `description` varchar(255) DEFAULT NULL,
  `insert_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `status` int(11) DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

/*Table structure for table `t_role_permission` */

DROP TABLE IF EXISTS `t_role_permission`;

CREATE TABLE `t_role_permission` (
  `role_id` bigint(20) NOT NULL,
  `permission_id` bigint(20) NOT NULL,
  PRIMARY KEY (`role_id`,`permission_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

/*Table structure for table `t_user` */

DROP TABLE IF EXISTS `t_user`;

CREATE TABLE `t_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` char(64) NOT NULL,
  `password` char(64) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  `mobile` char(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

/*Table structure for table `t_user_role` */

DROP TABLE IF EXISTS `t_user_role`;

CREATE TABLE `t_user_role` (
  `user_id` bigint(20) NOT NULL,
  `role_id` bigint(20) NOT NULL,
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
  • 获取权限SQL
SELECT * FROM t_permission WHERE id IN (
	SELECT permission_id FROM t_role_permission WHERE role_id IN (
		SELECT role_id FROM t_user_role WHERE user_id = (
			SELECT id FROM t_user WHERE username = 'zhangsan'
		)
	)
)
# 或者使用下面的SQL
SELECT * FROM t_permission AS p,t_role_permission AS rp,t_role AS r 
WHERE r.id=rp.role_id AND rp.permission_id=p.id AND r.id
IN
(SELECT r.id FROM t_user AS u,t_role AS r,t_user_role AS ur 
WHERE u.username ='zhangsan' AND u.id=ur.user_id AND ur.role_id=r.id);

web授权(url 拦截授权)

    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()     // 对请求进行验证
                .antMatchers("/role/**").permitAll()                //  开放授权
                .antMatchers("/admin/**").hasAuthority("ADMIN")    // 必须有ADMIN权限
                .antMatchers("/user/**").hasAnyAuthority("USER", "ADMIN")       //有任意一种权限
                .antMatchers("/xx/**").hasAnyRole("TK");         //  包含这个角色
    }

HttpSecurity 常用方法及说明

方法 说明
openidLogin() 用于基于 OpenId 的验证
headers() 将安全标头添加到响应
cors() 配置跨域资源共享( CORS )
sessionManagement() 允许配置会话管理
portMapper() 允许配置一个PortMapper(HttpSecurity#(getSharedObject(class))),其他提供SecurityConfigurer的对象使用 PortMapper 从 HTTP 重定向到 HTTPS 或者从 HTTPS 重定向到 HTTP。默认情况下,Spring Security使用一个PortMapperImpl映射 HTTP 端口8080到 HTTPS 端口8443,HTTP 端口80到 HTTPS 端口443
jee() 配置基于容器的预认证。 在这种情况下,认证由Servlet容器管理
x509() 配置基于x509的认证
rememberMe 允许配置“记住我”的验证
authorizeRequests() 允许基于使用HttpServletRequest限制访问
requestCache() 允许配置请求缓存
exceptionHandling() 允许配置错误处理
securityContext() 在HttpServletRequests之间的SecurityContextHolder上设置SecurityContext的管理。 当使用WebSecurityConfigurerAdapter时,这将自动应用
servletApi() 将HttpServletRequest方法与在其上找到的值集成到SecurityContext中。 当使用WebSecurityConfigurerAdapter时,这将自动应用
csrf() 添加 CSRF 支持,使用WebSecurityConfigurerAdapter时,默认启用
logout() 添加退出登录支持。当使用WebSecurityConfigurerAdapter时,这将自动应用。默认情况是,访问URL”/ logout”,使HTTP Session无效来清除用户,清除已配置的任何#rememberMe()身份验证,清除SecurityContextHolder,然后重定向到”/login?success”
anonymous() 允许配置匿名用户的表示方法。 当与WebSecurityConfigurerAdapter结合使用时,这将自动应用。 默认情况下,匿名用户将使用org.springframework.security.authentication.AnonymousAuthenticationToken表示,并包含角色 “ROLE_ANONYMOUS”
formLogin() 指定支持基于表单的身份验证。如果未指定FormLoginConfigurer#loginPage(String),则将生成默认登录页面
oauth2Login() 根据外部OAuth 2.0或OpenID Connect 1.0提供程序配置身份验证
requiresChannel() 配置通道安全。为了使该配置有用,必须提供至少一个到所需信道的映射
httpBasic() 配置 Http Basic 验证
addFilterAt() 在指定的Filter类的位置添加过滤器

方法授权(方法拦截授权)

基于方法的授权本质上是使用AOP的方法进行授权。所以我们可以在方法之前授权或者在方法之后授权。

  • @PreAuthorize(hasAuthority(权限名)) | @PreAuthorize(hasAnyAuthority(权限名...)) 方法之前
  • @PostAuthorize(hasAuthority(权限名)) |@PostAuthorize(hasAnyAuthority(权限名..)) 方法之后
使用方式

配置注解

@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)

Spring Boot项目地址 https://github.com/yusonghu/spring-security-demo


Spring Cloud Security OAuth2

介绍

Spring Security OAuth2 是对OAuth2的一种实现,OAuth2的服务提供方包括两个服务:

  1. 授权服务(认证服务)
  2. 资源服务

使用Spring Security OAuth2的时候可以选择他们在同一个应用中实现,也可以选择建立使用同一个授权服务的多个资源服务。

授权服务

  • AuthorizationEndpoint 服务于认证请求:默认URL:/oauth/authorize
  • TokenEndpoint 服务于访问令牌请求:默认URL:/oauth/token
  • OAuth2AuthenticationProcessingFilter 用来对请求给出的身份令牌解析鉴权

创建一个简单的微服务

目录模块如下图:

编写POM.xml

  • 父工程
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>spring-security-oauth2-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>spring-security-oauth2-uaa</module>
        <module>spring-security-oauth2-order</module>
        <module>spring-security-oauth2-gateway</module>
        <module>spring-security-oauth2-discovery</module>
    </modules>

    <packaging>pom</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencyManagement>
        <dependencies>

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>


            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>3.1.0</version>
                <scope>provided</scope>
            </dependency>

            <dependency>
                <groupId>javax.interceptor</groupId>
                <artifactId>javax.interceptor-api</artifactId>
                <version>1.2</version>
            </dependency>

            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.47</version>
            </dependency>

            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.0</version>
            </dependency>

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


            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-jwt</artifactId>
                <version>1.0.10.RELEASE</version>
            </dependency>


            <dependency>
                <groupId>org.springframework.security.oauth.boot</groupId>
                <artifactId>spring-security-oauth2-autoconfigure</artifactId>
                <version>2.1.3.RELEASE</version>
            </dependency>


        </dependencies>
    </dependencyManagement>



    <build>
        <finalName>${project.name}</finalName>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
                <includes>
                    <include>**/*</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
        <plugins>
            <!--<plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>-->

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>

            <plugin>
                <artifactId>maven-resources-plugin</artifactId>
                <configuration>
                    <encoding>utf-8</encoding>
                    <useDefaultDelimiters>true</useDefaultDelimiters>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
  • 授权认证服务(uaa)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-security-oauth2-demo</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-security-oauth2-uaa</artifactId>


    <dependencies>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>com.netflix.hystrix</groupId>
            <artifactId>hystrix-javanica</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
        </dependency>

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


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

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


        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-commons</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
        </dependency>

        <dependency>
            <groupId>javax.interceptor</groupId>
            <artifactId>javax.interceptor-api</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>


        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>


    </dependencies>
</project>
  • 订单服务(order)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-security-oauth2-demo</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-security-oauth2-order</artifactId>
    <dependencies>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

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

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

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.interceptor</groupId>
            <artifactId>javax.interceptor-api</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>


    </dependencies>

</project>
  • 网关(gateway)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-security-oauth2-demo</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-security-oauth2-gateway</artifactId>

    <dependencies>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>com.netflix.hystrix</groupId>
            <artifactId>hystrix-javanica</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
        </dependency>

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

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

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
        </dependency>

        <dependency>
            <groupId>javax.interceptor</groupId>
            <artifactId>javax.interceptor-api</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

    </dependencies>


</project>
  • 服务发现(discovery)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-security-oauth2-demo</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-security-oauth2-discovery</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

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

    </dependencies>
</project>

授权服务器配置

@Configuration
@EnableAuthorizationServer
public class Authorization extends AuthorizationServerConfigurerAdapter {
        @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        super.configure(clients);
        //  使用内存的方式
        clients.inMemory()          //  使用内存的方式
                .withClient("phone")       //  客户端id
                .secret(new BCryptPasswordEncoder().encode("secret"))               //  客户端密钥
                .resourceIds("res")             //  资源列表
                .authorizedGrantTypes("authorization_code", "password","client_credentials","implicit","refresh_token")// 该client允许的授权类型authorization_code,password,refresh_token,implicit,client_credentials
                .scopes("all")                  //  授权允许范围
                .autoApprove(false)             //  false 跳转到授权页面   true 直接发令牌
                .redirectUris("http://www.baidu.com");               //验证回调地址
    }
}

管理令牌

    //  令牌存储策略
	//	注入bean
    @Bean
    public TokenStore tokenStore(){
        //  基于内存的token
        return new InMemoryTokenStore();
    }
	


	@Autowired 
	private TokenStore tokenStore;

	
	 @Autowired
    private ClientDetailsService clientDetailsService;

    //  令牌访问服务
    public AuthorizationServerTokenServices tokenServices(){
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setClientDetailsService(clientDetailsService);
        defaultTokenServices.setSupportRefreshToken(true);      //    是否产生刷新令牌
        defaultTokenServices.setAccessTokenValiditySeconds(7200);   //      令牌有效期默认两小时
        defaultTokenServices.setRefreshTokenValiditySeconds(2592000);       //  刷新令牌默认有效期3天
        defaultTokenServices.setTokenStore(tokenStore);     //  令牌存储策略
        return defaultTokenServices;
    }

	    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;

    //  令牌访问端点
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        super.configure(endpoints);
        endpoints.authenticationManager(authenticationManager)              //  密码模式
                .authorizationCodeServices(authorizationCodeServices)       //  授权码模式
                .tokenServices(tokenServices())                             //  令牌管理服务
                .allowedTokenEndpointRequestMethods(HttpMethod.POST);        //  允许POST请求
    }

 //  令牌访问安全策略
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        super.configure(security);
        security.tokenKeyAccess("permitAll()")             //   /oauth/token_key        公开
                .checkTokenAccess("permitAll()")           //   /oauth/check_token      公开
                .allowFormAuthenticationForClients();      //允许表单提交
    }
  • Spring Security OAuth2 默认令牌端点
    • /oauth/authorize授权端点
    • /oauth/token令牌端点
    • /oauth/error授权服务错误信息端点
    • /oauth/confirm_access用户确认授权提交端点
    • /oauth/check_token用于资源服务访问的令牌解析端点
    • /oauth/token_key提供公有密匙的端点,如果使用JWT的话

之后可以把之前spring boot 的授权给拷贝过来。

授权服务配置总结:授权服务配置分成三大块,可以关联记忆。

既然要完成认证,它首先得知道客户端信息从哪儿读取,因此要进行客户端详情配置。

既然要颁发token,那必须得定义token的相关endpoint,以及token如何存取,以及客户端支持哪些类型的

token。

既然暴露除了一些endpoint,那对这些endpoint可以定义一些安全上的约束等。

授权码模式

介绍

  1. 资源拥有者打开客户端,客户端要求资源拥有者授权,重定向到授权服务器。/uaa/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=http://www.baidu.com 参数列表如下:
    • client_id:客户端准入标识
    • response_type:授权码模式固定为code
    • scope :客户端权限
    • redirect_uri:跳转uri,当授权码申请成功后会跳转到此地址,并在后边带上code参数(授权码)。
  2. 浏览器出现向授权服务器授权页面,之后将用户同意授权
  3. 授权服务器将授权码(AuthorizationCode)转经浏览器发送给client(通过redirect_uri)。
  4. 客户端拿着授权码向授权服务器索要访问access_token,请求如下:
  • 获取授权码url/uaa/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=http://www.baidu.com

  • 获取token(POST) /uaa/oauth/token?client_id=c1&client_secret=secret&grant_type=authorization_code&code=5PgfcD&redirect_uri=http://www.baidu.com

PS:授权码模式拿到的授权码获取一次token之后授权码失效。

简化模式

  1. 资源拥有者打开客户端,客户端要求资源拥有者给予授权,它将浏览器被重定向到授权服务器,重定向时会附加客户端的身份信息

/uaa/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=http://www.baidu.com

参数列表如下:

  • client_id:客户端准入标识。

  • response_type:授权码模式固定为code。

  • scope:客户端权限。

  • redirect_uri:跳转uri,当授权码申请成功后会跳转到此地址,并在后边带上code参数(授权码)。

  1. 浏览器出现向授权服务器授权页面,之后将用户同意授权。
  2. 授权服务器将授权码(AuthorizationCode)转经浏览器发送给client(通过redirect_uri)。
  3. 客户端拿着授权码向授权服务器索要访问access_token

/uaa/oauth/token?client_id=c1&client_secret=secret&grant_type=authorization_code&code=5PgfcD&redirect_uri=http://www.baidu.com

PS: 一般来说,简化模式用于没有服务端的第三方单页面应用,因为没有服务端,所以无法接收授权码。

密码模式

  1. 资源拥有者将用户名、密码发送给客户端
  2. 客户端拿着资源拥有者的用户名、密码向授权服务器请求令牌(access_token)

uaa/oauth/token?%20client_id=c1&client_secret=secret&grant_type=password&username=shangsan&password=123](http://uaa/oauth/token? client_id=c1&client_secret=secret&grant_type=password&username=shangsan&password=123)

参数列表如下:

  • client_id:客户端准入标识。

  • client_secret:客户端秘钥。

  • grant_type:授权类型,填写password表示密码模式

  • username:资源拥有者用户名。

  • password:资源拥有者密码。

  1. 授权服务器将令牌(access_token)发送给client

PS:这种模式十分简单,但是却意味着直接将用户敏感信息泄漏给了client,因此这就说明这种模式只能用于client是我 们自己开发的情况下。因此密码模式一般用于我们自己开发的,第一方原生App或第一方单页面应用。

客户端模式

  1. 客户端向授权服务器发送自己的身份信息,并请求令牌(access_token)
  2. 确认客户端身份无误后,将令牌(access_token)发送给client

/uaa/oauth/token?client_id=c1&client_secret=secret&grant_type=client_credentials

参数列表如下:

  • client_id:客户端准入标识。

  • client_secret:客户端秘钥。

  • grant_type:授权类型,填写client_credentials表示客户端模式

PS: 这种模式是最方便但最不安全的模式。因此这就要求我们对client完全的信任,而client本身也是安全的。因

此这种模式一般用来提供给我们完全信任的服务器端服务。比如,合作方系统对接,拉取一组用户信息。

添加资源服务

资源服务配置

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    //  资源ID        (这个得与授权服务得客户端得ResourceId 一致)
    public static final String RESOURCE_ID = "res1";

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        super.configure(resources);
        resources.resourceId(RESOURCE_ID)   //资源ID
                .tokenServices(tokenService())        //  验证令牌服务
                .stateless(true);           //  无状态
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.authorizeRequests().antMatchers("/**")
                .access("#oauth2.hasAnyScope('all')")                                   //  这个scope是授权服务器得授的授权范围
                .and().csrf().disable()                                                          //  禁用csrf
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);    //  不启用session
    }


    //  验证token
    @Bean
    public ResourceServerTokenServices tokenService() {
        //  远程服务请求授权服务器校验token,必须指定校验token、client_id、client_secret
        RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
        remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:53020/uaa/oauth/check_token");
        remoteTokenServices.setClientId("phone");			//	client_id
        remoteTokenServices.setClientSecret("secret");		//	client_secret
        return remoteTokenServices;
    }
}

资源服务安全拦截

这个这个配置就是服务内部的权限管理

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    //安全拦截机制(最重要)
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().authorizeRequests()
                .anyRequest().permitAll();
    }
}

JWT 令牌

介绍

JSON Web Token(JWT) 是一个开放的行业标准,它定义了一种简洁的、自包含的协议格式,用于在通信双方传递JSON对象。

官网:https://jwt.io/

标准:https://tools.ietf.org/html/rfc7519

结构

  • Header 头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA)

例如:

{ "alg": "HS256", "typ": "JWT" }
  • Payload 第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的现成字段,比 如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。

例如:

{ "sub": "1234567890", "name": "456", "admin": true }
  • Signature 第三部分是签名,此部分用于防止jwt内容被篡改。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

base64UrlEncode(header):jwt令牌的第一部分。

base64UrlEncode(payload):jwt令牌的第二部分。

secret:签名所使用的密钥。

将授权服务器颁发JWT令牌

  • 修改存储方式
    private String SINGING_KEY = "SIGIN_UAA";

    @Bean
    public TokenStore tokenStore() {
        //  JWT 令牌存储方案
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(SINGING_KEY);       //  对称密钥,资源服务器使用该密钥来验证
        return converter;
    }

  • 定义令牌服务
@Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;

  //  令牌访问服务
    @Bean
    public AuthorizationServerTokenServices tokenServices(){
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setClientDetailsService(clientDetailsService);
        defaultTokenServices.setSupportRefreshToken(true);      //    是否产生刷新令牌
        defaultTokenServices.setAccessTokenValiditySeconds(7200);   //      令牌有效期默认两小时
        defaultTokenServices.setRefreshTokenValiditySeconds(2592000);       //  刷新令牌默认有效期3天
        defaultTokenServices.setTokenStore(tokenStore);     //  令牌存储策略
        //  设置令牌增强
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter));
        defaultTokenServices.setTokenEnhancer(tokenEnhancerChain);
        return defaultTokenServices;
    }
  • 资源服务修改为本地校验

    • 拷贝一份TokenConfig到资源服务中校验
    @Configuration
    public class TokenConfig {
    
        private String SINGING_KEY = "SIGIN_UAA";
    
        @Bean
        public TokenStore tokenStore() {
            //  JWT 令牌存储方案
            return new JwtTokenStore(accessTokenConverter());
        }
    
        @Bean
        public JwtAccessTokenConverter accessTokenConverter() {
            JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
            converter.setSigningKey(SINGING_KEY);       //  对称密钥,资源服务器使用该密钥来验证
            return converter;
        }
    
    }
    
    • 将远程校验设置校验服务为本地校验
    @Autowired
        private TokenStore tokenStore;
    
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            super.configure(resources);
            resources.resourceId(RESOURCE_ID)   //资源ID
    
    //                .tokenServices(tokenService())        //  远程验证令牌服务
                    .tokenStore(tokenStore)                 //  本地验证令牌服务
                    .stateless(true);           //  无状态
        }
    

将客户端信息接入到数据库中

建表

  • 接入客户端信息
DROP TABLE IF EXISTS `oauth_client_details`;

CREATE TABLE `oauth_client_details` (
  `client_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '客户端标 识',
  `resource_ids` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '接入资源列表',
  `client_secret` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '客户端秘钥',
  `scope` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `authorized_grant_types` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `web_server_redirect_uri` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `authorities` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL,
  `refresh_token_validity` int(11) DEFAULT NULL,
  `additional_information` longtext CHARACTER SET utf8 COLLATE utf8_general_ci,
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `archived` tinyint(4) DEFAULT NULL,
  `trusted` tinyint(4) DEFAULT NULL,
  `autoapprove` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  PRIMARY KEY (`client_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='接入客户端信息';

/*Data for the table `oauth_client_details` */

insert  into `oauth_client_details`(`client_id`,`resource_ids`,`client_secret`,`scope`,`authorized_grant_types`,`web_server_redirect_uri`,`authorities`,`access_token_validity`,`refresh_token_validity`,`additional_information`,`create_time`,`archived`,`trusted`,`autoapprove`) values ('pc','res2','$2a$10$NlBC84MVb7F95EXYTXwLneXgCca6/GipyWR5NHm8K0203bSQMLpvm','ROLE_API','client_credentials,password,authorization_code,implicit,refresh_token','http://www.baidu.com',NULL,31536000,2592000,NULL,'2020-07-31 16:18:54',0,0,'false'),('phone','res1','$2a$10$NlBC84MVb7F95EXYTXwLneXgCca6/GipyWR5NHm8K0203bSQMLpvm','ROLE_ADMIN,ROLE_USER,ROLE_API','client_credentials,password,authorization_code,implicit,refresh_token','http://www.baidu.com',NULL,7200,259200,NULL,'2020-07-31 16:18:47',0,0,'false');

  • 存储授权码表
CREATE TABLE `oauth_code` (
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `authentication` blob,
  KEY `code_index` (`code`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
  • 官方给出的表SQL
DROP TABLE IF EXISTS `clientdetails`;

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;

/*Table structure for table `oauth_access_token` */

DROP TABLE IF EXISTS `oauth_access_token`;

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;

/*Table structure for table `oauth_approvals` */

DROP TABLE IF EXISTS `oauth_approvals`;

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;

/*Table structure for table `oauth_client_details` */

DROP TABLE IF EXISTS `oauth_client_details`;

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;

/*Table structure for table `oauth_client_token` */

DROP TABLE IF EXISTS `oauth_client_token`;

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;

/*Table structure for table `oauth_code` */

DROP TABLE IF EXISTS `oauth_code`;

CREATE TABLE `oauth_code` (
  `code` varchar(256) DEFAULT NULL,
  `authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Table structure for table `oauth_refresh_token` */

DROP TABLE IF EXISTS `oauth_refresh_token`;

CREATE TABLE `oauth_refresh_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Table structure for table `tb_permission` */

DROP TABLE IF EXISTS `tb_permission`;

CREATE TABLE `tb_permission` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `parent_id` bigint(20) DEFAULT NULL COMMENT '父权限',
  `name` varchar(64) NOT NULL COMMENT '权限名称',
  `enname` varchar(64) NOT NULL COMMENT '权限英文名称',
  `url` varchar(255) NOT NULL COMMENT '授权路径',
  `description` varchar(200) DEFAULT NULL COMMENT '备注',
  `created` datetime NOT NULL,
  `updated` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=44 DEFAULT CHARSET=utf8 COMMENT='权限表';

/*Table structure for table `tb_role` */

DROP TABLE IF EXISTS `tb_role`;

CREATE TABLE `tb_role` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `parent_id` bigint(20) DEFAULT NULL COMMENT '父角色',
  `name` varchar(64) NOT NULL COMMENT '角色名称',
  `enname` varchar(64) NOT NULL COMMENT '角色英文名称',
  `description` varchar(200) DEFAULT NULL COMMENT '备注',
  `created` datetime NOT NULL,
  `updated` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8 COMMENT='角色表';

/*Table structure for table `tb_role_permission` */

DROP TABLE IF EXISTS `tb_role_permission`;

CREATE TABLE `tb_role_permission` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `role_id` bigint(20) NOT NULL COMMENT '角色 ID',
  `permission_id` bigint(20) NOT NULL COMMENT '权限 ID',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=utf8 COMMENT='角色权限表';

/*Table structure for table `tb_user` */

DROP TABLE IF EXISTS `tb_user`;

CREATE TABLE `tb_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(64) NOT NULL COMMENT '密码,加密存储',
  `phone` varchar(20) DEFAULT NULL COMMENT '注册手机号',
  `email` varchar(50) DEFAULT NULL COMMENT '注册邮箱',
  `created` datetime NOT NULL,
  `updated` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`) USING BTREE,
  UNIQUE KEY `phone` (`phone`) USING BTREE,
  UNIQUE KEY `email` (`email`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8 COMMENT='用户表';

/*Table structure for table `tb_user_role` */

DROP TABLE IF EXISTS `tb_user_role`;

CREATE TABLE `tb_user_role` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) NOT NULL COMMENT '用户 ID',
  `role_id` bigint(20) NOT NULL COMMENT '角色 ID',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8 COMMENT='用户角色表';

ClientDetailService和AuthorizationCodeServices从数据库中读取

    //  将客户端信息从数据库中来
    @Bean
    public ClientDetailsService clientDetailsService(DataSource dataSource) {
        ClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
        ((JdbcClientDetailsService) clientDetailsService).setPasswordEncoder(passwordEncoder);
        return clientDetailsService;
    }
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        super.configure(clients);
        //  使用数据库的方式
        clients.withClientDetails(clientDetailsService);
    }

将授权码存入数据库

    @Bean
    public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource){
        //  设置授权码模式的授权码如何存取
        return new JdbcAuthorizationCodeServices(dataSource);
    }

接入网关

网关资源服务配置

@Configuration
public class ResourceServerConfig {
    //  资源ID        (这个得与授权服务得客户端得ResourceId 一致)
    public static final String RESOURCE_ID = "res1";

    /**
     * Uaa资源配置
     */
    @Configuration
    @EnableResourceServer
    public class UaaServerConfig extends ResourceServerConfigurerAdapter{

        @Autowired
        private TokenStore tokenStore;

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            super.configure(resources);
            resources.resourceId(RESOURCE_ID)   //资源ID
                    .tokenStore(tokenStore)                 //  本地验证令牌服务
                    .stateless(true);           //  无状态
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            super.configure(http);
            //  将uaa全部放行
            http.authorizeRequests().antMatchers("/uaa/**")
                    .permitAll()
                    .and().csrf().disable()                                                          //  禁用csrf
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);    //  无状态
        }
    }

    /**
     * Order资源配置
     */
    @Configuration
    @EnableResourceServer
    public class OrderServerConfig extends ResourceServerConfigurerAdapter{
        @Autowired
        private TokenStore tokenStore;

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            super.configure(resources);
            resources.resourceId(RESOURCE_ID)   //资源ID
                    .tokenStore(tokenStore)                 //  本地验证令牌服务
                    .stateless(true);           //  无状态
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            super.configure(http);
            //  将uaa全部放行
            http.authorizeRequests().antMatchers("/order/**")
                    //  order路径需要ROLE_API 权限
                    .access("#oauth2.hasAnyScope('ROLE_API')")                                   //  这个scope是授权服务器得授的授权范围
                    .and().csrf().disable()                                                          //  禁用csrf
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);    //  无状态
        }
    }

}

PS:记得把TokenConfig和WebSecurityConfig拷贝过来,记得将所有路径放行

网关转发token并解析jwt

  • 添加网关过滤器
public class AuthFilter extends ZuulFilter {

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext currentContext = RequestContext.getCurrentContext();
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (!(authentication instanceof OAuth2Authentication)) {
            return null;
        }
        OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) authentication;
        //  获取当前用户身份信息
        String principal = oAuth2Authentication.getName();
        //  获取当前用户权限信息
        List<String> authorities = new ArrayList<>();
        oAuth2Authentication.getAuthorities().stream().forEach(v -> authorities.add(v.getAuthority()));
        //  把身份和权限信息放在json中,加入http的header中
        OAuth2Request oAuth2Request = oAuth2Authentication.getOAuth2Request();
        Map<String, String> requestParameters = oAuth2Request.getRequestParameters();
        Map<String,Object> jsonToken = new HashMap<>(requestParameters);
        if (oAuth2Authentication != null) {
            jsonToken.put("principal",principal);
            jsonToken.put("authorities",authorities);
        }
        currentContext.addZuulRequestHeader("json-token", Base64.encode(JSON.toJSONString(jsonToken)));
        //  转发给微服务
        return null;
    }
}

  • 添加zuul配置
@Configuration
public class ZuulConfig {
    @Bean
    public AuthFilter preFileter() {
        return new AuthFilter();
    }

    @Bean
    public FilterRegistrationBean corsFilter() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        config.setMaxAge(18000L);
        source.registerCorsConfiguration("/**", config);
        CorsFilter corsFilter = new CorsFilter(source);
        FilterRegistrationBean bean = new FilterRegistrationBean(corsFilter);
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return bean;
    }
}

  • 给资源解析从网关解析出来的token放进去。
@Component
public class TokenAuthenticationFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        //  解析头重的token
        String token = httpServletRequest.getHeader("json-token");
        if (token != null) {
            String tokenJSON = Base64.decodeStr(token);
            //  将token转成json对象
            JSONObject jsonObject = JSON.parseObject(tokenJSON);
            //  用户身份信息
            String principal = jsonObject.getString("principal");
            UserDTO userDTO = JSON.parseObject(principal, UserDTO.class);
            //  用户权限
            JSONArray jsonArray = jsonObject.getJSONArray("authorities");
            String[] authorities = jsonArray.toArray(new String[jsonArray.size()]);
            //  将用户信息和权限填充到用户身份token对象重
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDTO,null, AuthorityUtils.createAuthorityList(authorities));
            //  创建spring security detail
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
            //  将authenticationToken填充到安全上下文
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }
        filterChain.doFilter(httpServletRequest,httpServletResponse);
    }
}

  • 在资源中获取用户信息。
 UserDTO userDTO = (UserDTO) SecurityContextHolder.getContext().getAuthentication().getPrincipal();

项目地址:https://github.com/yusonghu/spring-security-oauth2-demo

原文地址:https://www.cnblogs.com/bananafish/p/13413215.html