Spring Security oAuth2

Spring Security

Spring Security是基于javaEE实现的企业级认证授权框架,底层采用Filter链实现认证授权。和spring boot整合后简易使用。

在这里插入图片描述

Spring Security认证流程

在这里插入图片描述
1. 用户提交用户名密码
2. 经过UsernamePasswordAuthenticationFilter认证过滤器,交给AuthenticationManager认证。
3. AuthenticationManager交给AuthenticationProvider(DaoAuthenticationProvider)认证
4. AuthenticationProvider通过UserDetailsService的loadUserByUsername()方法获取用户信息UserDetails
5. 通过PasswordEncoder对比输入的密码和UserDetails的密码是否一致,给Authentication增加认证信息
6. 将Authentication认证信息通过SecurityContextHolder.getContext().setAuthentication()方法保存到上下文

因此,可以写一个UserDetailsService的实现类,将用户的身份信息放入。

Spring Security授权流程

通过SecurityConfig继承WebSecurityConfigurerAdapter,实现configure(HttpSecurity http),可以用http.authorizeRequests()
对web请求进行授权保护,原理为采用标准的Filter对web请求拦截,实现资源授权访问
  1. 用户访问受保护资源,要经过FilterSecurityInterceptor
  2. 然后通过SecurityMetadataSource.getAttributes()获取当前资源所需的权限,返回Collection<ConfigAttribute>
  3. AccessDecisonManager.Decide()投票决策是否通过,允许访问

web授权(基于url)

web授权尽量采用继续权限的授权而不是基于角色。路径范围大的放最后(和拦截器相同)避免出现问题。
http.csrf().disable()
.authorizeRequests()
.antMatchers("/level1/").hasRole(“VIP1”)
.antMatchers("/level2/
").hasRole(“VIP2”)
.antMatchers("/level3/").hasRole(“VIP3”)
.antMatchers("/
").permitAll();

方法授权(基于注解)

SecurityConfig配置类添加@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)开启。
方法上有@PreAuthorize、@PostAuthorize、@Secured (不常用)三种

	@PreAuthorize("hasRole('VIP1')")
    @GetMapping("/say")
    public String say(){
        System.out.println("say");
        return "ok";
    }

    @PreAuthorize("hasRole('VIP1') and hasRole('VIP2')")
    @GetMapping("/sayHi")
    public String sayHi(){
        System.out.println("say hi");
        return "ok";
    }

Spring Security会话

Spring Security登录后,信息存储在SecurityContextHolder中,可以从中获取UserDetails对象等

public String getUsername() {
        String username = null;
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        Object principal = authentication.getPrincipal();
        // 未认证
        if (principal == null) {
            username = "匿名";
        } else {
            if (principal instanceof UserDetails) {
                UserDetails userDetails = (UserDetails) principal;
                username = userDetails.getUsername();
            } else {
                username = principal.toString();
            }
        }
        return username;
    }

会话存在四种状态,可以通过http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)来设置
1. always 没有session就创建一个
2. ifRequired 需要时创建
3. never 不创建,如果有就使用
4. stateless 不创建,也不使用 分布式系统使用无状态令牌token时,不需要session

Spring Security 实现

1. 导入spring-boot-starter-security
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
2. 添加配置类SecurityConfig,继承WebSecurityConfigurerAdapter,内部有密码编译器规则,用户信息认证,授权
	2.1 基于内存实现
package com.shyb.config;

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

/**
 * @EnableWebSecurity 开启SpringSecurity  改注解带了@Configuration 会被扫描到
 */
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //定制授权规则 路径范围大的放最后拦截
        // 1. csrf().disable()关闭自定义页面有CSRF跨站请求伪造的发生,限制除GET以外大多数请求
        // 2. 也可以采用增加隐藏token也解决CSRF问题
        http.csrf().disable()
        		.authorizeRequests()
                .antMatchers("/level1/**").hasRole("VIP1")
                .antMatchers("/level2/**").hasRole("VIP2")
                .antMatchers("/level3/**").hasRole("VIP3")
                .antMatchers("/**").permitAll();
        //开启自动配置的登录功能  没有登陆会到登录页面,登录成功后跳转到输入的网页处
        //1. 默认到/login来到登录页
        //2. 登录失败重定向到/login?error页面
        // 自定义login页面  loginPage
        // 默认/login Get请求是到登录页面  Post是登录  默认账户密码为username和password,
        //可以usernameParameter("mobile").passwordParameter("pwd");指定参数
        http.formLogin().loginPage("/login").usernameParameter("mobile").passwordParameter("pwd");
        // 开启自己注销功能
        // 1. 访问/logout标识用户自动注销
        // 2. 规定是post请求,所以需要form表单指定method
        // 3. 注销后会跳转到/login?logout页面
        // logoutSuccessUrl主要成功后的页面
        http.logout().logoutSuccessUrl("/");
        // 开启记住我功能
        // 登录成功后,将Cookie发给浏览器保存,以后访问会带上这个Cookie,通过检查就可以免登陆
        // 点击注销会删除Cookie  默认保存时间是14// 自定义记住我时,使用rememberMeParameter("remember")来接受参数
        http.rememberMe().rememberMeParameter("remember");
    }

    /**
     * 密码编译器
     * @return
     */
    @Bean
    PasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 用户信息认证(内存)
     * @return
     */
    @Override
    @Bean
    public UserDetailsService userDetailsService(){
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("zhangsan").password(bCryptPasswordEncoder().encode("123456")).roles("VIP1","VIP2").build());
        manager.createUser(User.withUsername("lisi").password(bCryptPasswordEncoder().encode("123456")).roles("VIP2","VIP3").build());
        manager.createUser(User.withUsername("wangwu").password(bCryptPasswordEncoder().encode("123456")).authorities("create","p1").build());
        return manager;
    }
}

	2.2 基于数据库实现(写UserDetailsServiceImpl实现类实现UserDetailsService,实现loadUserByUsername方法),上面
	SecurityConfig的userDetailsService()删除
package com.shyb.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

/**
 * @author wzh
 * @date 2019/12/19 - 8:24
 */
@Service
public class UserDetailServiceImpl implements UserDetailsService {
    @Autowired
    PasswordEncoder bCryptPasswordEncoder;
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        System.out.println("username:"+s);
        //模拟数据库查询 正常时注入UserMapper,根据用户名查询用户
        //查询为null时,直接返回null即可
        //查询不为null,调用下面方法将账号、密码、权限、身份设置进UserDetails,security会去检验密码和权限
        UserDetails userDetails = User.withUsername("zhangsan")
                .password(bCryptPasswordEncoder.encode("123456")).roles("VIP1","VIP3").build();
        return userDetails;
    }
}

分布式服务认证

1. 随着项目的复杂度上升,分布式服务越来越多,每个服务都部署认证太过冗余。
2. 第三方服务访问,也需要授权(类似微信、支付宝)
3. 认证方式必须可扩展(账号密码、短信验证码、二维码、人脸识别等)。
因此,可以单独部署认证服务器,所有认证服务都需请求认证服务器,OAuth2协议就能解决上面问题。

OAuth2简介

oAuth 协议是为用户资源的授权的标准。允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不需要
将账号和密码提供给第三方应用或分享他们数据的所有内容。

在这里插入图片描述

OAuth2环境介绍

  1. 采用springcloud微服务,依赖spring-cloud-starter-oauth2
  2. OAuth2提供TokenEndpoint访问令牌请求。默认URL: /oauth/token
  3. AuthorizationEndpoint服务于认证请求。默认URL: /oauth/authorize
  4. 资源服务器要校验token的合法性,使用的是OAuth2AuthenticationProcessingFilter

OAuth2使用

OAuth2要在Spring Security认证基础上,增加授权服务器配置

  1. 在config包下增加AuthorizationServerConfig类,继承AuthorizationServerConfigurerAdapter,并增加@EnableAuthorizationServer
  2. OAuth2客户端授权方式有四种
    1. 简化模式(不常用)
    2. 授权码模式(适用第三方接入)
      授权码模式用户确认后,认证服务器会返回code,第三方服务通过code、client_id、client_secret请求认证服务器获得access_token和refresh_token。code只能使用一次。
    3. 密码模式(适用内部系统认证)
      密码模式会泄露密码,因此不能用于接第三方系统。
    4. 客户端模式(不常用)
  3. 实现AuthorizationServerConfigurerAdapter的三个方法,默认token生成策略为内存模式,可以通过注入bean修改。密码模式需要在WebSecurityConfig中将父类authenticationManager()重写注入spring。授权码模式需要注入AuthorizationCodeServices
  4. 令牌访问与刷新
    1. Access Token 是客户端访问资源服务器的令牌。这个授权应该是 临时 的,有一定
      有效期。因为,Access Token 在使用的过程中 可能会泄露。然而引入了有效期之后,
      每当 Access Token 过期,客户端就必须重新向用户索要授权。非常影响用户体验。
      oAuth2.0 引入了 Refresh Token 机制
    2. Refresh Token 的作用是用来刷新 Access Token。认证服务器提供一个刷新接口。
      Refresh Token 一定是保存在客户端的服务器上 。oAuth2.0 引入了 client_secret
      机制。客户端必须把 client_secret 妥善保管在服务器上,刷新 Access Token 时,
      需要验证这个 client_secret。

授权码模式请求流程

1. 访问获取授权码 
ip:port/oauth/authorize?client_id=client&response_type=code
2. 授权页面通过,地址栏会增加参数code=MesLkD
3. PostMan请求/oauth/token,Post请求,参数有client_id,client_secret,grant_type=authorization_code,code=MesLkD
基于内存实现

application.yml

spring:
  application:
    name: oauth2-server
server:
  port: 8080

授权服务器

@EnableAuthorizationServer
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    BCryptPasswordEncoder passwordEncoder;
    @Autowired
    ClientDetailsService clientDetailsService;
    @Autowired
    AuthenticationManager authenticationManager;

    //令牌生成方案  默认在内存中生成普通令牌
    @Bean
    public TokenStore tokenStore(){
        return new InMemoryTokenStore();
    }

    // 配置客户端详情信息服务 客户端通过client_id和client_secret来访问资源
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("client")// client_id
                .secret(passwordEncoder.encode("secret"))// client_secret
                .authorizedGrantTypes("authorization_code")// 授权类型,共有authorization_code,password,client_credentials,implicit,refresh_token五种
                .scopes("app")// 授权范围
                .autoApprove(false)// false跳转到授权页面  true 直接发令牌
                .redirectUris("http://www.baidu.com");// 注册回调地址
    }

    // 令牌访问服务
    @Bean
    public AuthorizationServerTokenServices tokenServices(){
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setClientDetailsService(clientDetailsService);//客户端信息服务
        tokenServices.setTokenStore(tokenStore());// 令牌生成方案
        tokenServices.setRefreshTokenValiditySeconds(7200);//令牌默认有效期2小时
        tokenServices.setAccessTokenValiditySeconds(259200);// 刷新令牌默认有效期3天
        tokenServices.setSupportRefreshToken(true);// 设置刷新令牌
        return tokenServices;
    }

    // 授权码模式
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(){
        return new InMemoryAuthorizationCodeServices();
    }

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

    // 令牌访问策略
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("permitAll()")     //oauth/token_key公开
                .checkTokenAccess("permitAll()")  //oauth/check_token公开
                .allowFormAuthenticationForClients();//允许表单认证,申请令牌
    }
}

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true,jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

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

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

}
@Service
public class UserDetailServiceImpl implements UserDetailsService {
    @Autowired
    PasswordEncoder bCryptPasswordEncoder;
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        //实际UserDetails根据s用户名去数据库查询,将结果放入UserDetails中,系统会去比较
        System.out.println("username:"+s);
        UserDetails userDetails = User.withUsername("zhangsan")
                .password(bCryptPasswordEncoder.encode("123456")).roles("VIP1","VIP3").build();
        return userDetails;
    }
}
基于jdbc存储token

和基于内存的区别:

  1. 增加DataSource的配置
  2. 令牌存储方案TokenStore 修改为JdbcTokenStore
  3. 客户端读取设置ClientDetailsService修改为JdbcClientDetailsService,将上面ClientDetails的配置写入oauth_client_details表中
    在这里插入图片描述
  4. 令牌访问端点的令牌服务管理设置(已写)
  5. 配置客户端信息ClientDetailsServiceConfigurer,使用withClientDetails
    默认URL
  6. 添加spring-boot-starter-jdbc和mysql-connector-java的依赖

/oauth/authorize:授权端点
/oauth/token:令牌端点
/oauth/confirm_access:用户确认授权提交端点
/oauth/error:授权服务错误信息端点
/oauth/check_token:用于资源服务访问的令牌解析端点
/oauth/token_key:提供公有密匙的端点,如果你使用 JWT 令牌的话

oauth2数据库sql
application.yml

spring:
  application:
    name: oauth2-server
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    username: root
    password: yali
    url: jdbc:mysql://localhost:3306/oauth2?characterEncoding=utf-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&useSSL=true&zeroDateTimeBehavior=convertToNull&autoReconnect = true
    hikari:
      maximum-pool-size: 20
      max-lifetime: 30000
      idle-timeout: 30000
      data-source-properties:
        prepStmtCacheSize: 250
        prepStmtCacheSqlLimit: 2048
        cachePrepStmts: true
        useServerPrepStmts: true
server:
  port: 8080

@EnableAuthorizationServer
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    BCryptPasswordEncoder passwordEncoder;
    @Autowired
    AuthenticationManager authenticationManager;
    @Autowired
    HikariDataSource hikariDataSource;

    //令牌生成方案  默认在内存中生成普通令牌
    @Bean
    public TokenStore tokenStore(){
        return new JdbcTokenStore(hikariDataSource);
    }

    public ClientDetailsService jdbcClientDetailsService(){
        return new JdbcClientDetailsService(hikariDataSource);
    }

    // 配置客户端详情信息服务 客户端通过client_id和client_secret来访问资源
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(jdbcClientDetailsService());
    }

    // 令牌访问服务
    @Bean
    public AuthorizationServerTokenServices tokenServices(){
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setClientDetailsService(jdbcClientDetailsService());//客户端信息服务
        tokenServices.setTokenStore(tokenStore());// 令牌生成方案
        tokenServices.setRefreshTokenValiditySeconds(7200);//令牌默认有效期2小时
        tokenServices.setAccessTokenValiditySeconds(259200);// 刷新令牌默认有效期3天
        tokenServices.setSupportRefreshToken(true);// 设置刷新令牌
        return tokenServices;
    }

    // 授权码模式
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(){
        return new JdbcAuthorizationCodeServices(hikariDataSource);
    }

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

    // 令牌访问策略
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("permitAll()")     //oauth/token_key公开
                .checkTokenAccess("permitAll()")  //oauth/check_token公开
                .allowFormAuthenticationForClients();//允许表单认证,申请令牌
    }
}
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true,jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

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

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

}
@Service
public class UserDetailServiceImpl implements UserDetailsService {
    @Autowired
    PasswordEncoder bCryptPasswordEncoder;
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        //实际UserDetails根据s用户名去数据库查询,将结果放入UserDetails中,系统会去比较 参考下面RBAC
        System.out.println("username:"+s);
        UserDetails userDetails = User.withUsername("zhangsan")
                .password(bCryptPasswordEncoder.encode("123456")).roles("VIP1","VIP3").build();
        return userDetails;
    }
}
RBAC基于角色的权限控制

在基于JDBC的基础上,增加RBAC角色控制

  1. 引入mybatis-spring-boot-starter
  2. 引入mapper-spring-boot-starter(使用tk.mybatis)
  3. 执行下面sql脚本
/*!40101 SET NAMES utf8 */;

/*!40101 SET SQL_MODE=''*/;

/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`oauth2` /*!40100 DEFAULT CHARACTER SET utf8 */;

USE `oauth2`;

/*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='权限表';

/*Data for the table `tb_permission` */

insert  into `tb_permission`(`id`,`parent_id`,`name`,`enname`,`url`,`description`,`created`,`updated`) values 

(1,0,'系统管理','System','/',NULL,'2019-12-24 12:45:33','2019-12-24 12:45:33'),

(2,1,'用户管理','SystemUser','/users/',NULL,'2019-12-24 12:45:33','2019-12-24 12:45:33'),

(3,2,'查看用户','SystemUserView','',NULL,'2019-12-24 12:45:33','2019-12-24 12:45:33'),

(4,2,'新增用户','SystemUserInsert','',NULL,'2019-12-24 12:45:33','2019-12-24 12:45:33'),

(5,2,'编辑用户','SystemUserUpdate','',NULL,'2019-12-24 12:45:33','2019-12-24 12:45:33'),

(6,2,'删除用户','SystemUserDelete','',NULL,'2019-12-24 12:45:33','2019-12-24 12:45:33');

/*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='角色表';

/*Data for the table `tb_role` */

insert  into `tb_role`(`id`,`parent_id`,`name`,`enname`,`description`,`created`,`updated`) values 

(1,0,'超级管理员','admin',NULL,'2019-12-24 12:44:58','2019-12-24 12:45:00');

/*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='角色权限表';

/*Data for the table `tb_role_permission` */

insert  into `tb_role_permission`(`id`,`role_id`,`permission_id`) values 

(1,1,1),

(2,1,2),

(3,1,3),

(4,1,4),

(5,1,5),

(6,1,6);

/*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='用户表';

/*Data for the table `tb_user` */

insert  into `tb_user`(`id`,`username`,`password`,`phone`,`email`,`created`,`updated`) values 

(1,'admin','$2a$10$9ZhDOBp.sRKat4l14ygu/.LscxrMUcDAfeVOEPiYwbcRkoB09gCmi','15888888888','aaaa@qq.com','2019-12-24 12:44:47','2019-12-24 12:44:49');

/*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='用户角色表';

/*Data for the table `tb_user_role` */

insert  into `tb_user_role`(`id`,`user_id`,`role_id`) values 

(1,1,1);

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

application.yml

spring:
  application:
    name: oauth2-server
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    username: root
    password: yali
    url: jdbc:mysql://localhost:3306/oauth2?characterEncoding=utf-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&useSSL=true&zeroDateTimeBehavior=convertToNull&autoReconnect = true
    hikari:
      maximum-pool-size: 20
      max-lifetime: 30000
      idle-timeout: 30000
      data-source-properties:
        prepStmtCacheSize: 250
        prepStmtCacheSqlLimit: 2048
        cachePrepStmts: true
        useServerPrepStmts: true
server:
  port: 8080
mybatis:
  mapper-locations: classpath:mapper/*.xml

启动类扫描mapper

@SpringBootApplication
@MapperScan(basePackages = "com.wzh.spring.security.oauth2.sever.mapper")
public class OAuth2ServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(OAuth2ServerApplication.class,args);
    }
}

查询和授权

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    TbUserService tbUserService;
    @Autowired
    TbPermissionService tbPermissionService;
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        TbUser tbUser = tbUserService.getByUsername(s);
        if (tbUser != null) {
            // 获取用户授权
            List<TbPermission> tbPermissions = tbPermissionService.selectByUserId(tbUser.getId());
            List<SimpleGrantedAuthority> collect = tbPermissions.stream().map(TbPermission::getEnname).map(SimpleGrantedAuthority::new).collect(Collectors.toList());
            // 由框架完成认证工作
            return User.withUsername(tbUser.getUsername()).password(tbUser.getPassword()).authorities(collect).build();
        }
        return null;
    }
}

完整代码在最后。

基于redis存储token令牌

与基于JDBC的区别:

  1. 增加spring-boot-starter-data-redis依赖
  2. application.yml增加redis配置
  3. 授权服务器注入RedisConnectionFactory,JdbcTokenStore改为RedisTokenStore
spring:
  application:
    name: oauth2-server
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    username: root
    password: yali
    url: jdbc:mysql://localhost:3306/oauth2?characterEncoding=utf-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&useSSL=true&zeroDateTimeBehavior=convertToNull&autoReconnect = true
    hikari:
      maximum-pool-size: 20
      max-lifetime: 30000
      idle-timeout: 30000
      data-source-properties:
        prepStmtCacheSize: 250
        prepStmtCacheSqlLimit: 2048
        cachePrepStmts: true
        useServerPrepStmts: true
  redis:
    host: localhost
    password: 123456
    port: 6379

server:
  port: 8080
mybatis:
  mapper-locations: classpath:mapper/*.xml
@EnableAuthorizationServer
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    BCryptPasswordEncoder passwordEncoder;
    @Autowired
    AuthenticationManager authenticationManager;
    @Autowired
    HikariDataSource hikariDataSource;
    @Autowired
    RedisConnectionFactory redisConnectionFactory;
    //令牌生成方案  默认在内存中生成普通令牌
    @Bean
    public TokenStore tokenStore(){
        return new RedisTokenStore(redisConnectionFactory);
    }

    public ClientDetailsService jdbcClientDetailsService(){
        return new JdbcClientDetailsService(hikariDataSource);
    }

    // 配置客户端详情信息服务 客户端通过client_id和client_secret来访问资源
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(jdbcClientDetailsService());
    }

    // 令牌访问服务
    @Bean
    public AuthorizationServerTokenServices tokenServices(){
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setClientDetailsService(jdbcClientDetailsService());//客户端信息服务
        tokenServices.setTokenStore(tokenStore());// 令牌生成方案
        tokenServices.setRefreshTokenValiditySeconds(7200);//令牌默认有效期2小时
        tokenServices.setAccessTokenValiditySeconds(259200);// 刷新令牌默认有效期3天
        tokenServices.setSupportRefreshToken(true);// 设置刷新令牌
        return tokenServices;
    }

    // 授权码模式
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(){
        return new JdbcAuthorizationCodeServices(hikariDataSource);
    }

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

    // 令牌访问策略
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("permitAll()")     //oauth/token_key公开
                .checkTokenAccess("permitAll()")  //oauth/check_token公开
                .allowFormAuthenticationForClients();//允许表单认证,申请令牌
    }
}
资源服务器模块
流程图

在这里插入图片描述

  1. 增加一个服务spring-security-oauth2-resource
  2. 数据库增加内容管理权限模块,执行下面sql脚本
insert  into `tb_permission`(`id`,`parent_id`,`name`,`enname`,`url`,`description`,`created`,`updated`) values 
(7,1,'内容管理','SystemContent','/contents/',NULL,'2019-12-24 12:45:33','2019-12-24 12:45:33'),
(8,7,'查看内容','SystemContentView','/contents/view/**',NULL,'2019-12-24 12:45:33','2019-12-24 12:45:33'),
(9,7,'新增内容','SystemContentInsert','/contents/insert/**',NULL,'2019-12-24 12:45:33','2019-12-24 12:45:33'),
(10,7,'编辑内容','SystemContentUpdate','/contents/update/**',NULL,'2019-12-24 12:45:33','2019-12-24 12:45:33'),
(11,7,'删除内容','SystemContentDelete','/contents/delete/**',NULL,'2019-12-24 12:45:33','2019-12-24 12:45:33');
insert  into `tb_role_permission`(`id`,`role_id`,`permission_id`) values 
(7,1,7),
(8,1,8),
(9,1,9),
(10,1,10),
(11,1,11);
  1. 增加内容管理表和数据
CREATE TABLE `tb_content` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `category_id` bigint(20) NOT NULL COMMENT '内容类目ID',
  `title` varchar(200) DEFAULT NULL COMMENT '内容标题',
  `sub_title` varchar(100) DEFAULT NULL COMMENT '子标题',
  `title_desc` varchar(500) DEFAULT NULL COMMENT '标题描述',
  `url` varchar(500) DEFAULT NULL COMMENT '链接',
  `pic` varchar(300) DEFAULT NULL COMMENT '图片绝对路径',
  `pic2` varchar(300) DEFAULT NULL COMMENT '图片2',
  `content` text COMMENT '内容',
  `created` datetime DEFAULT NULL,
  `updated` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `category_id` (`category_id`),
  KEY `updated` (`updated`)
) ENGINE=InnoDB AUTO_INCREMENT=42 DEFAULT CHARSET=utf8;
insert  into `tb_content`(`id`,`category_id`,`title`,`sub_title`,`title_desc`,`url`,`pic`,`pic2`,`content`,`created`,`updated`) values 
(28,89,'标题','子标题','标题说明','http://www.jd.com',NULL,NULL,NULL,'2019-04-07 00:56:09','2019-04-07 00:56:11'),
(29,89,'ad2','ad2','ad2','http://www.baidu.com',NULL,NULL,NULL,'2019-04-07 00:56:13','2019-04-07 00:56:15'),
(30,89,'ad3','ad3','ad3','http://www.sina.com.cn',NULL,NULL,NULL,'2019-04-07 00:56:17','2019-04-07 00:56:19'),
(31,89,'ad4','ad4','ad4','http://www.funtl.com',NULL,NULL,NULL,'2019-04-07 00:56:22','2019-04-07 00:56:25');

CREATE TABLE `tb_content_category` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '类目ID',
  `parent_id` bigint(20) DEFAULT NULL COMMENT '父类目ID=0时,代表的是一级的类目',
  `name` varchar(50) DEFAULT NULL COMMENT '分类名称',
  `status` int(1) DEFAULT '1' COMMENT '状态。可选值:1(正常),2(删除)',
  `sort_order` int(4) DEFAULT NULL COMMENT '排列序号,表示同级类目的展现次序,如数值相等则按名称次序排列。取值范围:大于零的整数',
  `is_parent` tinyint(1) DEFAULT '1' COMMENT '该类目是否为父类目,1为true,0为false',
  `created` datetime DEFAULT NULL COMMENT '创建时间',
  `updated` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`),
  KEY `parent_id` (`parent_id`,`status`) USING BTREE,
  KEY `sort_order` (`sort_order`)
) ENGINE=InnoDB AUTO_INCREMENT=98 DEFAULT CHARSET=utf8 COMMENT='内容分类';
insert  into `tb_content_category`(`id`,`parent_id`,`name`,`status`,`sort_order`,`is_parent`,`created`,`updated`) values 
(30,0,'LeeShop',1,1,1,'2015-04-03 16:51:38','2015-04-03 16:51:40'),
(86,30,'首页',1,1,1,'2015-06-07 15:36:07','2015-06-07 15:36:07'),
(87,30,'列表页面',1,1,1,'2015-06-07 15:36:16','2015-06-07 15:36:16'),
(88,30,'详细页面',1,1,1,'2015-06-07 15:36:27','2015-06-07 15:36:27'),
(89,86,'大广告',1,1,0,'2015-06-07 15:36:38','2015-06-07 15:36:38'),
(90,86,'小广告',1,1,0,'2015-06-07 15:36:45','2015-06-07 15:36:45'),
(91,86,'商城快报',1,1,0,'2015-06-07 15:36:55','2015-06-07 15:36:55'),
(92,87,'边栏广告',1,1,0,'2015-06-07 15:37:07','2015-06-07 15:37:07'),
(93,87,'页头广告',1,1,0,'2015-06-07 15:37:17','2015-06-07 15:37:17'),
(94,87,'页脚广告',1,1,0,'2015-06-07 15:37:31','2015-06-07 15:37:31'),
(95,88,'边栏广告',1,1,0,'2015-06-07 15:37:56','2015-06-07 15:37:56'),
(96,86,'中广告',1,1,1,'2015-07-25 18:58:52','2015-07-25 18:58:52'),
(97,96,'中广告1',1,1,0,'2015-07-25 18:59:43','2015-07-25 18:59:43');
  1. 增删改查业务正常写
  2. 配置资源服务器ResourceServerConfig继承ResourceServerConfigurerAdapter
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true,jsr250Enabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
	@Override
    public void configure(HttpSecurity http) throws Exception {
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/insert").hasAuthority("SystemContentInsert")
                .antMatchers("/update").hasAuthority("SystemContentUpdate");
    }
}

application.yml

spring:
  application:
    name: oauth2-resource
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    username: root
    password: yali
    url: jdbc:mysql://localhost:3306/oauth2?characterEncoding=utf-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&useSSL=true&zeroDateTimeBehavior=convertToNull&autoReconnect = true
    hikari:
      maximum-pool-size: 20
      max-lifetime: 30000
      idle-timeout: 30000
      data-source-properties:
        prepStmtCacheSize: 250
        prepStmtCacheSqlLimit: 2048
        cachePrepStmts: true
        useServerPrepStmts: true

server:
  port: 8081
  servlet:
    context-path: contents
security:
  oauth2:
    client:
      client-id: client
      client-secret: secret
      access-token-uri: http://localhost:8080/oauth/token
      user-authorization-uri: http://localhost:8080/oauth/authorize
    resource:
      token-info-uri: http://localhost:8080/oauth/check_token
mybatis:
  mapper-locations: classpath:mapper/*.xml

请求

@RestController
public class TbContentController {

    @Autowired
    TbContentService tbContentService;

    @GetMapping("/view")
    @PreAuthorize("hasAuthority('SystemContentView')")
    public ResponseResult<List<TbContent>> list(){
        return new ResponseResult<>(HttpStatus.OK.value(),HttpStatus.OK.toString(),tbContentService.selectAll());
    }
}

源码

github源码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
水资源是人类社会的宝贵财富,在生活、工农业生产中是不可缺少的。随着世界人口的增长及工农业生产的发展,需水量也在日益增长,水已经变得比以往任何时候都要珍贵。但是,由于人类的生产和生活,导致水体的污染,水质恶化,使有限的水资源更加紧张。长期以来,油类物质(石油类物质和动植物油)一直是水和土壤中的重要污染源。它不仅对人的身体健康带来极大危害,而且使水质恶化,严重破坏水体生态平衡。因此各国都加强了油类物质对水体和土壤的污染的治理。对于水中油含量的检测,我国处于落后阶段,与国际先进水平存在差距,所以难以满足当今技术水平的要求。为了取得具有代表性的正确数据,使分析数据具有与现代测试技术水平相应的准确性和先进性,不断提高分析成果的可比性和应用效果,检测的方法和仪器是非常重要的。只有保证了这两方面才能保证快速和准确地测量出水中油类污染物含量,以达到保护和治理水污染的目的。开展水中油污染检测方法、技术和检测设备的研究,是提高水污染检测的一条重要措施。通过本课题的研究,探索出一套适合我国国情的水质污染现场检测技术和检测设备,具有广泛的应用前景和科学研究价值。 本课题针对我国水体的油污染,探索一套检测油污染的可行方案和方法,利用非分散红外光度法技术,开发研制具有自主知识产权的适合国情的适于野外便携式的测油仪。利用此仪器,可以检测出被测水样中亚甲基、甲基物质和动植物油脂的污染物含量,为我国众多的环境检测站点监测水体的油污染状况提供依据。
### 内容概要 《计算机试卷1》是一份综合性的计算机基础和应用测试卷,涵盖了计算机硬件、软件、操作系统、网络、多媒体技术等多个领域的知识点。试卷包括单选题和操作应用两大类,单选题部分测试学生对计算机基础知识的掌握,操作应用部分则评估学生对计算机应用软件的实际操作能力。 ### 适用人群 本试卷适用于: - 计算机专业或信息技术相关专业的学生,用于课程学习或考试复习。 - 准备计算机等级考试或职业资格认证的人士,作为实战演练材料。 - 对计算机操作有兴趣的自学者,用于提升个人计算机应用技能。 - 计算机基础教育工作者,作为教学资源或出题参考。 ### 使用场景及目标 1. **学习评估**:作为学校或教育机构对学生计算机基础知识和应用技能的评估工具。 2. **自学测试**:供个人自学者检验自己对计算机知识的掌握程度和操作熟练度。 3. **职业发展**:帮助职场人士通过实际操作练习,提升计算机应用能力,增强工作竞争力。 4. **教学资源**:教师可以用于课堂教学,作为教学内容的补充或学生的课后练习。 5. **竞赛准备**:适合准备计算机相关竞赛的学生,作为强化训练和技能检测的材料。 试卷的目标是通过系统性的题目设计,帮助学生全面复习和巩固计算机基础知识,同时通过实际操作题目,提高学生解决实际问题的能力。通过本试卷的学习与练习,学生将能够更加深入地理解计算机的工作原理,掌握常用软件的使用方法,为未来的学术或职业生涯打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值