• 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

  • 相关阅读:
    RabbitMQ 入门
    Spring boot 2.x 中使用redis
    spring boot 中 Cache 的使用
    vbs 入门
    移动端文本框被原生键盘弹出后挡住文本框
    HTML中添加音乐video embed audio
    input修改placeholder文字颜色
    vue中更换.ico图标报错路径找不到图片
    Chrome表单文本框自动填充黄色背景色样式
    请求头缺少 'Access-Control-Allow-Origin'
  • 原文地址:https://www.cnblogs.com/bananafish/p/13413215.html
Copyright © 2020-2023  润新知