Spring Security:安全框架
OAuth2.0:用于分布式认证授权
JWT:与OAuth2.0相关的令牌
系统为什么要认证
输入支付密码,是二次认证
会话的2种方式
1:基于session方式
2:基于token方式
比如微信认证(登录)后,是否绑定银行卡==>是否可以发红包
授权的2种资源
功能资源:系统菜单、页面、按钮、代码方法
实体(数据)资源:2类
who(主体) what(资源) how(权限)
以上是6张数据表的关系
优化后的授权数据模型
企业常用RBAC实现授权
按角色进行授权(Role-Based Access Controller)
案例:
if(主体.hasRole("总经理角色id")){
查询工资
}
问题:如果查询工资的角色变化为总经理和部门经理,还有项目经理可访问,此时就需要修改判断逻辑
if(主体.hasRole("总经理角色id") || 主体.hasRole("部门经理角色id") || 主体.hasRole("项目经理角色id")){
查询工资
}
根据上边案例发现,当需要修改角色的权限时就需要修改授权的相关代码,系统可拓展性差
按资源进行授权(Resource-Based Access Controller)
代码案例:
if(主体.hasPermission("查询公司权限标识")){
查询工资
}
优点:系统设计时定义好查询工资的权限标识,即使查询工资所需要的角色变化,都不需要修改授权代码,系统可扩展性强
自己写登录页面
自己写登录时,校验账户和密码
自己写拦截器
拦截器进行拦截请求,资源分配
有包的版本问题,建议和项目上一致
<packaging>warpackaging>//spring是基于war包形式的
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<maven.compiler.source>1.8maven.compiler.source>
<maven.compiler.target>1.8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-webartifactId>
<version>5.1.4.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-configartifactId>
<version>5.1.4.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>5.1.5.RELEASEversion>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.0.1version>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.8version>
dependency>
dependencies>
<build>
<finalName>security-springmvcfinalName>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.tomcat.mavengroupId>
<artifactId>tomcat7-maven-pluginartifactId>
<version>2.2version>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<configuration>
<source>1.8source>
<target>1.8target>
configuration>
plugin>
<plugin>
<artifactId>maven-resources-pluginartifactId>
<configuration>
<encoding>utf-8encoding>
<useDefaultDelimiters>trueuseDefaultDelimiters>
<resources>
<resource>
<directory>src/main/resourcesdirectory>
<filtering>truefiltering>
<includes>
<include>**/*include>
includes>
resource>
<resource>
<directory>src/main/javadirectory>
<filtering>truefiltering>
<includes>
<include>**/*.xmlinclude>
includes>
resource>
resources>
configuration>
plugin>
plugins>
pluginManagement>
build>
导入依赖
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.3.RELEASEversion>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<maven.compiler.source>1.8maven.compiler.source>
<maven.compiler.target>1.8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<scope>providedscope>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>jstlartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
dependency>
<dependency>
<groupId>org.apache.tomcat.embedgroupId>
<artifactId>tomcat-embed-jasperartifactId>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.8version>
dependency>
dependencies>
<build>
<finalName>security-springbootfinalName>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.tomcat.mavengroupId>
<artifactId>tomcat7-maven-pluginartifactId>
<version>2.2version>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<configuration>
<source>1.8source>
<target>1.8target>
configuration>
plugin>
<plugin>
<artifactId>maven-resources-pluginartifactId>
<configuration>
<encoding>utf-8encoding>
<useDefaultDelimiters>trueuseDefaultDelimiters>
<resources>
<resource>
<directory>src/main/resourcesdirectory>
<filtering>truefiltering>
<includes>
<include>**/*include>
includes>
resource>
<resource>
<directory>src/main/javadirectory>
<filtering>truefiltering>
<includes>
<include>**/*.xmlinclude>
includes>
resource>
resources>
configuration>
plugin>
plugins>
pluginManagement>
build>
server:
port: 8080
servlet:
context-path: /security-springboot #工程路径
spring:
application:
name: security-springboot #工程名字
#视图解析器
mvc:
view:
prefix: /WEB-INF/views/ #前缀
suffix: .jsp #后缀
springboot项目不需要 applicationContext.xml (spring的配置)----用application.yaml代替
spring security解决的问题就是安全访问控制
spring security对web资源的保护是靠多个Filter实现的
5个数据表
/*
Navicat Premium Data Transfer
Source Server : lsx_localhost
Source Server Type : MySQL
Source Server Version : 50528
Source Host : localhost:3306
Source Schema : user_db
Target Server Type : MySQL
Target Server Version : 50528
File Encoding : 65001
Date: 28/09/2020 22:18:51
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for t_permission
-- ----------------------------
DROP TABLE IF EXISTS `t_permission`;
CREATE TABLE `t_permission` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `code`(`code`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of t_permission
-- ----------------------------
INSERT INTO `t_permission` VALUES (1, 'p1', '测试资源1', '/r/r1');
INSERT INTO `t_permission` VALUES (2, 'p2', '测试资源2', '/r/r2');
INSERT INTO `t_permission` VALUES (3, 'p3', '测试资源3', '/r/r3');
-- ----------------------------
-- Table structure for t_role
-- ----------------------------
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role` (
`id` bigint(32) NOT NULL AUTO_INCREMENT,
`role_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`create_time` datetime NULL DEFAULT NULL,
`update_time` datetime NULL DEFAULT NULL,
`status` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `role_name`(`role_name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of t_role
-- ----------------------------
INSERT INTO `t_role` VALUES (1, 'root', '管理员', NULL, NULL, NULL);
INSERT INTO `t_role` VALUES (2, 'user', '员工', NULL, NULL, NULL);
-- ----------------------------
-- Table structure for t_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `t_role_permission`;
CREATE TABLE `t_role_permission` (
`role_id` int(11) NOT NULL,
`permission_id` int(11) NOT NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of t_role_permission
-- ----------------------------
INSERT INTO `t_role_permission` VALUES (1, 1);
INSERT INTO `t_role_permission` VALUES (1, 2);
INSERT INTO `t_role_permission` VALUES (1, 3);
INSERT INTO `t_role_permission` VALUES (2, 1);
-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`fullname` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户姓名',
`mobile` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '手机号码',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES (1, 'lsx', '$2a$10$0eNRxImm.3JuvSOxR9vz6uqZ3n9V26pSG2yHw7JFRjhSI9GFhjhNy', '张三', '15675288363');
INSERT INTO `t_user` VALUES (2, 'carter', '$2a$10$0eNRxImm.3JuvSOxR9vz6uqZ3n9V26pSG2yHw7JFRjhSI9GFhjhNy', '李四', '15675288363');
-- ----------------------------
-- Table structure for t_user_role
-- ----------------------------
DROP TABLE IF EXISTS `t_user_role`;
CREATE TABLE `t_user_role` (
`user_id` int(11) NOT NULL,
`role_id` int(11) NOT NULL,
`create_time` datetime NULL DEFAULT NULL,
`update_time` datetime NULL DEFAULT NULL,
PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Records of t_user_role
-- ----------------------------
INSERT INTO `t_user_role` VALUES (1, 1, NULL, NULL);
SET FOREIGN_KEY_CHECKS = 1;
注解
@PreAuthorize :方法执行前拦截
@PostAuthorize:方法执行后拦截
@Secured:不推荐使用
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)//开启3种注解
共享性(统一)、开放性(第三方)
分布式每个服务都有认证、授权的需求,每个服务都实现一套认证逻辑会非常冗余
统一的认证授权
应用接入认证
基于session的认证方式
Session复制:多台应用服务企器之间同步session,使session保持一致性,对外透明
Session黏贴:当用户访问集群中某台服务器后,强制指定后续的所有请求均落到此机器上
Session集中存储:将session存入分布式缓存中,所有服务器应用实例统一从分布式缓存中存取session
优点:更好的在服务端对会话进行控制,且安全性高
缺点:session机制方式基于cookie,无法跨域,另外随着系统的扩展,需要提高session复制、黏贴、和集中式存储的容错性
基于token的认证方式
优点:容易维护、扩展性强
缺点:token包含的数据量大,每次请求都需要传递,占宽带。token签名验证对cpu产生处理负担
专门为第三方系统认证所设计的认证协议
允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息
JWT令牌格式
案例:微信登录(第三方授权访问)
<packaging>pompackaging>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.3.RELEASEversion>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>1.8project.reporting.outputEncoding>
<java.version>1.8java.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Greenwich.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.1.0version>
<scope>providedscope>
dependency>
<dependency>
<groupId>javax.interceptorgroupId>
<artifactId>javax.interceptor-apiartifactId>
<version>1.2version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.47version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.12version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.49version>
dependency>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-jwtartifactId>
<version>1.0.10.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.security.oauth.bootgroupId>
<artifactId>spring-security-oauth2-autoconfigureartifactId>
<version>2.1.3.RELEASEversion>
dependency>
dependencies>
dependencyManagement>
<build>
<finalName>${project.name}finalName>
<resources>
<resource>
<directory>src/main/resourcesdirectory>
<filtering>truefiltering>
<includes>
<include>**/*include>
includes>
resource>
<resource>
<directory>src/main/javadirectory>
<filtering>truefiltering>
<includes>
<include>**/*.xmlinclude>
includes>
resource>
resources>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<configuration>
<source>1.8source>
<target>1.8target>
configuration>
plugin>
<plugin>
<artifactId>maven-resources-pluginartifactId>
<configuration>
<encoding>utf-8encoding>
<useDefaultDelimiters>trueuseDefaultDelimiters>
configuration>
plugin>
plugins>
build>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-ribbonartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>com.netflix.hystrixgroupId>
<artifactId>hystrix-javanicaartifactId>
dependency>
<dependency>
<groupId>org.springframework.retrygroupId>
<artifactId>spring-retryartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-freemarkerartifactId>
dependency>
<dependency>
<groupId>org.springframework.datagroupId>
<artifactId>spring-data-commonsartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-oauth2artifactId>
dependency>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-jwtartifactId>
dependency>
<dependency>
<groupId>javax.interceptorgroupId>
<artifactId>javax.interceptor-apiartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
dependencies>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-oauth2artifactId>
dependency>
<dependency>
<groupId>javax.interceptorgroupId>
<artifactId>javax.interceptor-apiartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
dependencies>
/**
* 授权服务配置
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
@Autowired
private ClientDetailsService clientDetailsService;
@Autowired
private TokenStore tokenStore;
@Autowired
private AuthorizationCodeServices authorizationCodeServices;
@Autowired
private AuthenticationManager authenticationManager;
//1:客户端信息服务
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()//内存中
.withClient("c1")//客户端唯一标识
.secret(new BCryptPasswordEncoder().encode("secret"))//客户端秘钥
.resourceIds("res1")//资源标识
//authorization_code:授权码 password:密码模式 client_credentials客户端模式 implicit:简要模式 refresh_token:刷新令牌
.authorizedGrantTypes("authorization_code","password","client_credentials","implicit","refresh_token")//授权类型
.scopes("all")//允许范围
.autoApprove(false)//跳转到授权页面
.redirectUris("http://www.baidu.com");//客户端会跳地址
}
//2:管理令牌服务
@Bean
public AuthorizationServerTokenServices tokenServices(){
DefaultTokenServices services = new DefaultTokenServices();
services.setClientDetailsService(clientDetailsService);
services.setTokenStore(tokenStore);
services.setSupportRefreshToken(true);
services.setAccessTokenValiditySeconds(7200);//令牌默认有效期2小时
services.setRefreshTokenValiditySeconds(259200);//刷新令牌默认有效期3天
return services;
}
//设置 【授权码模式】 的授权服务
@Bean
public AuthorizationCodeServices authorizationCodeServices(){
return new InMemoryAuthorizationCodeServices();
}
//3:令牌的访问端点
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)//认证管理器---WebSecurityConfig中配置
.authorizationCodeServices(authorizationCodeServices)//授权码服务
.tokenServices(tokenServices())//令牌管理服务
.allowedTokenEndpointRequestMethods(HttpMethod.POST);
}
//4:安全约束---拦截器
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()")//这个url是公开的---用户不用登陆直接使用 url:/oauth/token_key
.checkTokenAccess("permitAll()")//远程校验token 的合法性 url:/oauth/check_token
.allowFormAuthenticationForClients();//允许表单申请令牌
}
}
//令牌管理
@Configuration
public class TokenConfig {
@Bean
public TokenStore tokenStore(){
return new InMemoryTokenStore();
}
}
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)//开启方法授权注解
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//认证管理器
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManagerBean();
}
//定义密码编码器---spring security自带加密
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
//拦截用户的请求---安全拦截截止(拦截器)
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()//屏蔽csrf请求问题
.authorizeRequests()
.antMatchers("/r/r1").hasAnyAuthority("p1")
.antMatchers("/login/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin();
}
}
@Service
public class SpringDataUserDetailsService implements UserDetailsService {
@Autowired
private UserDao userDao;
//根据账号 查询用户信息
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("前端用户输入的账号---->"+username);
//连接数据库 根据账号查询用户信息
UserDto userDto = userDao.getUserByUsername(username);
if (userDto == null){
return null;
}
//根据用户的id查询用户的权限
List<String> permissions = userDao.findPermissionByUserId(userDto.getId());
//将它转成数组
String[] permissionArray = new String[permissions.size()];
permissions.toArray(permissionArray);
//将权限标识符添加到UserDetails的authorities中区
UserDetails userDetails = User.withUsername(userDto.getUsername())
.password(userDto.getPassword())
.authorities(permissionArray).build();
return userDetails;
}
}
@Repository
public class UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
//根据账号 查询用户信息
public UserDto getUserByUsername(String username){
String sql = "select id,username,password,fullname,mobile from t_user where username = ?";
List<UserDto> list = jdbcTemplate.query(sql, new Object[]{
username}, new BeanPropertyRowMapper<>(UserDto.class));
if (list != null && list.size() == 1){
return list.get(0);
}
return null;
}
//根据用户的id查询用户的权限
public List<String> findPermissionByUserId(String userId){
String sql ="select * from t_permission WHERE id IN(\n" +
"select permission_id from t_role_permission where role_id IN(\n" +
"\tselect role_id from t_user_role where user_id = ? \n" +
") \n" +
")";
List<PermissionDto> query = jdbcTemplate.query(sql, new Object[]{
userId}, new BeanPropertyRowMapper<>(PermissionDto.class));
List<String> list = new ArrayList<>();
query.forEach(c -> list.add(c.getCode()));//传入权限标识符号
return list;
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PermissionDto {
private String id;
private String code;
private String description;
private String url;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDto {
private String id;
private String username;
private String password;
private String fullname;
private String mobile;
}
7.启动类
@SpringBootApplication
@EnableDiscoveryClient//服务注册与发现
@EnableHystrix//熔断
@EnableFeignClients(basePackages = {
"com.carter.security.distributed.uaa"})//远程调用
public class UaaSpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(UaaSpringbootApplication.class,args);
}
}
/uaa/oauth/authorize?client_id=c1&response_type=code&scope=ROLE_ADMIN&redirect_uri=http://www.baidu.com
/uaa/oauth/token?client_id=c1&client_secret=secret&grant_type=authorization_code&code=5Pgfsc&redirect_uri=http://www.baidu.com
Drop table if exists oauth_client_details;
create table oauth_client_details (
client_id VARCHAR(255) PRIMARY KEY,
resource_ids VARCHAR(255),
client_secret VARCHAR(255),
scope VARCHAR(255),
authorized_grant_types VARCHAR(255),
web_server_redirect_uri VARCHAR(255),
authorities VARCHAR(255),
access_token_validity INTEGER,
refresh_token_validity INTEGER,
additional_information TEXT,
create_time timestamp default now(),
archived tinyint(1) default '0',
trusted tinyint(1) default '0',
autoapprove VARCHAR (255) default 'false'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Drop table if exists oauth_code;
create table oauth_code (
create_time timestamp default now(),
code VARCHAR(255),
authentication BLOB,
index code_index (code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
第三方认证通过
安全性最高
Get请求直接获取
/uaa/oauth/authorize?
client_id=c1&
response_type=token&
scope=all&
redirect_uri=http://www.baidu.com
基于token的认证方式,每次都需要进行远程调用密码校验,影响性能
所以采用JWT令牌方式解决此问题(JSON WEB TOKEN),本身存储签名信息
JWT令牌
优点:
1)jwt基于json,非常方便解析
2)可以在令牌中自定义丰富的内容,易拓展
3)通过非对称加密算法以及数字签名技术,JWT防止篡改,安全性高
4)资源服务使用JWT不依赖认证服务即可完成授权
缺点:
JWT令牌比较长,占用存储空间比较大
头部信息+内容+签名(防止篡改)
Post密码模式获取令牌 【仅用于内测人员】
Get获取授权码code
http://localhost:53020/uaa/oauth/authorize?client_id=c1&response_type=code&scope=ROLE_ADMIN&redirect_uri=http://www.baidu.com
POST申请令牌
/uaa/oauth/token?client_id=c1&client_secret=secret&grant_type=authorization_code&code=5Pgfsc&redirect_uri=http://www.baidu.com
GET请求
/uaa/oauth/authorize?
client_id=c1&
response_type=token&
scope=all&
redirect_uri=http://www.baidu.com
distributed-security-discovery
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
dependencies>
配置文件
spring:
application:
name: distributed-discovery
server:
port: 53000 #启动端口
eureka:
server:
enable-self-preservation: false #关闭服务器自我保护,客户端心跳检测15分钟内错误达到80%服务会保护,导致别人还认为是好用的服务
eviction-interval-timer-in-ms: 10000 #清理间隔(单位毫秒,默认是60*1000)5秒将客户端剔除的服务在服务注册列表中剔除#
shouldUseReadOnlyResponseCache: true #eureka是CAP理论种基于AP策略,为了保证强一致性关闭此切换CP 默认不关闭 false关闭
client:
register-with-eureka: false #false:不作为一个客户端注册到注册中心
fetch-registry: false #为true时,可以启动,但报异常:Cannot execute request on any known server
instance-info-replication-interval-seconds: 10
serviceUrl:
defaultZone: http://localhost:${server.port}/eureka/
instance:
hostname: ${spring.cloud.client.ip-address}
prefer-ip-address: true
instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${spring.application.instance_id:${server.port}}
启动类
@SpringBootApplication
@EnableEurekaServer //这是一个注册中心Eureka的服务
public class DiscoveryApplication {
public static void main(String[] args) {
SpringApplication.run(DiscoveryApplication.class,args);
}
}
distributed-security-order资源的配置
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
spring.application.name=order-service
server.port=53021
spring.main.allow-bean-definition-overriding = true
#日志
logging.level.root = debug
logging.level.org.springframework.web = info
spring.http.encoding.enabled = true
spring.http.encoding.charset = UTF-8
spring.http.encoding.force = true
server.tomcat.remote_ip_header = x-forwarded-for
server.tomcat.protocol_header = x-forwarded-proto
server.use-forward-headers = true
server.servlet.context-path = /order
#视图信息
spring.freemarker.enabled = true
spring.freemarker.suffix = .html
spring.freemarker.request-context-attribute = rc
spring.freemarker.content-type = text/html
spring.freemarker.charset = UTF-8
spring.mvc.throw-exception-if-no-handler-found = true
spring.resources.add-mappings = false
#注册中心
eureka.client.serviceUrl.defaultZone = http://localhost:53000/eureka/
eureka.instance.preferIpAddress = true
eureka.instance.instance-id = ${spring.application.name}:${spring.cloud.client.ip-address}:${spring.application.instance_id:${server.port}}
management.endpoints.web.exposure.include = refresh,health,info,env
#远程调用
feign.hystrix.enabled = true
feign.compression.request.enabled = true
feign.compression.request.mime-types[0] = text/xml
feign.compression.request.mime-types[1] = application/xml
feign.compression.request.mime-types[2] = application/json
feign.compression.request.min-request-size = 2048
feign.compression.response.enabled = true
uaa服务同上。。。
distributed-security-gateway
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-ribbonartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>com.netflix.hystrixgroupId>
<artifactId>hystrix-javanicaartifactId>
dependency>
<dependency>
<groupId>org.springframework.retrygroupId>
<artifactId>spring-retryartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-zuulartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-oauth2artifactId>
dependency>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-jwtartifactId>
dependency>
<dependency>
<groupId>javax.interceptorgroupId>
<artifactId>javax.interceptor-apiartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
dependencies>
spring.application.name=gateway-server
server.port=53010
spring.main.allow-bean-definition-overriding = true
logging.level.root = info
logging.level.org.springframework = info
zuul.retryable = true
zuul.ignoredServices = *
zuul.add-host-header = true
zuul.sensitiveHeaders = *
zuul.routes.uaa-service.stripPrefix = false
zuul.routes.uaa-service.path = /uaa/**
zuul.routes.order-service.stripPrefix = false
zuul.routes.order-service.path = /order/**
eureka.client.serviceUrl.defaultZone = http://localhost:53000/eureka/
eureka.instance.preferIpAddress = true
eureka.instance.instance-id = ${spring.application.name}:${spring.cloud.client.ip-address}:${spring.application.instance_id:${server.port}}
management.endpoints.web.exposure.include = refresh,health,info,env
feign.hystrix.enabled = true
feign.compression.request.enabled = true
feign.compression.request.mime-types[0] = text/xml
feign.compression.request.mime-types[1] = application/xml
feign.compression.request.mime-types[2] = application/json
feign.compression.request.min-request-size = 2048
feign.compression.response.enabled = true
启动类
@SpringBootApplication
@EnableZuulProxy
@EnableDiscoveryClient
public class GatewayServerApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayServerApplication.class,args);
}
}
1:一种是认证服务器UAA生成jwt令牌,所有请求在网关层验证,判断权限等操作
2:一种是由各资源服务处理,网关只做请求转发
TokenConfig配置jwt令牌模式
//令牌管理
@Configuration
public class TokenConfig {
private String SIGNING_KEY = "uaa123";
@Bean
public TokenStore tokenStore() {
//JWT令牌存储方案---资源服务器不需要进行远程校验
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY); //对称秘钥,【资源服务器】使用该秘钥来验证
return converter;
}
}
ResourceServerConfig配置资源验证
@Configuration
public class ResourceServerConfig {
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){
resources.tokenStore(tokenStore).resourceId(RESOURCE_ID)
.stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/uaa/**").permitAll();
}
}
//order资源服务配置
@Configuration
@EnableResourceServer
public class OrderServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources){
resources.tokenStore(tokenStore).resourceId(RESOURCE_ID)
.stateless(true);
}
//服务进来直接进行验证
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/order/**").access("#oauth2.hasScope('ROLE_API')");
}
}
//配置其它的资源服务..
}
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//放行所有
http
.authorizeRequests()
.antMatchers("/**").permitAll()
.and().csrf().disable();
}
}
public class AuthFilter extends ZuulFilter {
@Override
public boolean shouldFilter() {
return true;
}
//请求之前的拦截
@Override
public String filterType() {
return "pre";
}
//优先级
@Override
public int filterOrder() {
return 0;
}
@Override
public Object run() throws ZuulException {
//从安全的上下文提取对象
RequestContext context = RequestContext.getCurrentContext();
//取出对象
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (!(authentication instanceof OAuth2Authentication)){
return null;//表示不是OAuth2格式的令牌
}
//转成OAuth2格式
OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) authentication;
//获取当前用户对象
Authentication userAuthentication = oAuth2Authentication.getUserAuthentication();
//1、获取当前用户的身份信息
String principal = userAuthentication.getName();
//2、获取当前用户的权限信息
List<String> authorities = new ArrayList<>();
userAuthentication.getAuthorities().stream().forEach(c->authorities.add(c.getAuthority()));//通过stream流的方式进行权限遍历
//2.1 其他oauth2.0请求的数据
OAuth2Request oAuth2Request = oAuth2Authentication.getOAuth2Request();
Map<String, String> requestParameters = oAuth2Request.getRequestParameters();//得到所有的参数信息
Map<String,Object> jsonToken = new HashMap<>(requestParameters);//后期可能会放对象所以用Object
if (userAuthentication != null){
jsonToken.put("principal",principal);//身份
jsonToken.put("authorities",authorities);//权限
}
//3、把身份信息和权限信息放到json中,加入http的header中,转发给微服务
context.addZuulRequestHeader("json-Token", EncryptUtil.encodeUTF8StringBase64(JSON.toJSONString(jsonToken)));
return null;
}
}
public class EncryptUtil {
private static final Logger logger = LoggerFactory.getLogger(EncryptUtil.class);
public static String encodeBase64(byte[] bytes){
String encoded = Base64.getEncoder().encodeToString(bytes);
return encoded;
}
public static byte[] decodeBase64(String str){
byte[] bytes = null;
bytes = Base64.getDecoder().decode(str);
return bytes;
}
public static String encodeUTF8StringBase64(String str){
String encoded = null;
try {
encoded = Base64.getEncoder().encodeToString(str.getBytes("utf-8"));
} catch (UnsupportedEncodingException e) {
logger.warn("不支持的编码格式",e);
}
return encoded;
}
public static String decodeUTF8StringBase64(String str){
String decoded = null;
byte[] bytes = Base64.getDecoder().decode(str);
try {
decoded = new String(bytes,"utf-8");
}catch(UnsupportedEncodingException e){
logger.warn("不支持的编码格式",e);
}
return decoded;
}
public static String encodeURL(String url) {
String encoded = null;
try {
encoded = URLEncoder.encode(url, "utf-8");
} catch (UnsupportedEncodingException e) {
logger.warn("URLEncode失败", e);
}
return encoded;
}
public static String decodeURL(String url) {
String decoded = null;
try {
decoded = URLDecoder.decode(url, "utf-8");
} catch (UnsupportedEncodingException e) {
logger.warn("URLDecode失败", e);
}
return decoded;
}
public static void main(String [] args){
String str = "abcd{'a':'b'}";
String encoded = EncryptUtil.encodeUTF8StringBase64(str);
String decoded = EncryptUtil.decodeUTF8StringBase64(encoded);
System.out.println(str);
System.out.println(encoded);
System.out.println(decoded);
String url = "== wo";
String urlEncoded = EncryptUtil.encodeURL(url);
String urlDecoded = EncryptUtil.decodeURL(urlEncoded);
System.out.println(url);
System.out.println(urlEncoded);
System.out.println(urlDecoded);
}
}
@Configuration
@EnableResourceServer
public class ResouceServerConfig extends ResourceServerConfigurerAdapter {
public static final String RESOURCE_ID = "res1";
@Autowired
TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID)//资源 id
.tokenStore(tokenStore)//jwt令牌
// .tokenServices(tokenService())//验证令牌的服务---远程调用解析
.stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/**").access("#oauth2.hasScope('ROLE_ADMIN')")
.and().csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
//资源服务令牌解析服务---远程调用解析
/* @Bean
public ResourceServerTokenServices tokenService() {
//使用远程服务请求授权服务器校验token,必须指定校验token 的url、client_id,client_secret
RemoteTokenServices service=new RemoteTokenServices();
service.setCheckTokenEndpointUrl("http://localhost:53020/uaa/oauth/check_token");
service.setClientId("c1");
service.setClientSecret("secret");
return service;
}*/
}
@Configuration
public class TokenConfig {
private String SIGNING_KEY = "uaa123";
@Bean
public TokenStore tokenStore() {
//JWT令牌存储方案
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY); //对称秘钥,资源服务器使用该秘钥来验证
return converter;
}
/* @Bean
public TokenStore tokenStore() {
//使用内存存储令牌(普通令牌)
return new InMemoryTokenStore();
}*/
}
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//安全拦截机制(最重要)
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
// .antMatchers("/r/r1").hasAuthority("p2") web授权访问形势
// .antMatchers("/r/r2").hasAuthority("p2")
.antMatchers("/r/**").authenticated()//所有/r/**的请求必须认证通过
.anyRequest().permitAll();//除了/r/**,其它的请求可以访问
}
}
资源的过滤器TokenAuthenticationFilter
解析头文件中的token,将一些信息给security
@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 json = EncryptUtil.decodeUTF8StringBase64(token);
//将token转成json对象
JSONObject jsonObject = JSON.parseObject(json);
//用户身份信息
UserDTO userDTO = JSON.parseObject(jsonObject.getString("principal"), UserDTO.class);
//用户权限
JSONArray authoritiesArray = jsonObject.getJSONArray("authorities");
String[] authorities = authoritiesArray.toArray(new String[authoritiesArray.size()]);
//将用户信息和权限填充 到用户身份token对象中
UsernamePasswordAuthenticationToken authenticationToken
= new UsernamePasswordAuthenticationToken(userDTO,null, AuthorityUtils.createAuthorityList(authorities));
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
//将authenticationToken填充到安全上下文
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
filterChain.doFilter(httpServletRequest,httpServletResponse);
}
}
解码工具类
public class EncryptUtil {
private static final Logger logger = LoggerFactory.getLogger(EncryptUtil.class);
public static String encodeBase64(byte[] bytes){
String encoded = Base64.getEncoder().encodeToString(bytes);
return encoded;
}
public static byte[] decodeBase64(String str){
byte[] bytes = null;
bytes = Base64.getDecoder().decode(str);
return bytes;
}
public static String encodeUTF8StringBase64(String str){
String encoded = null;
try {
encoded = Base64.getEncoder().encodeToString(str.getBytes("utf-8"));
} catch (UnsupportedEncodingException e) {
logger.warn("不支持的编码格式",e);
}
return encoded;
}
public static String decodeUTF8StringBase64(String str){
String decoded = null;
byte[] bytes = Base64.getDecoder().decode(str);
try {
decoded = new String(bytes,"utf-8");
}catch(UnsupportedEncodingException e){
logger.warn("不支持的编码格式",e);
}
return decoded;
}
public static String encodeURL(String url) {
String encoded = null;
try {
encoded = URLEncoder.encode(url, "utf-8");
} catch (UnsupportedEncodingException e) {
logger.warn("URLEncode失败", e);
}
return encoded;
}
public static String decodeURL(String url) {
String decoded = null;
try {
decoded = URLDecoder.decode(url, "utf-8");
} catch (UnsupportedEncodingException e) {
logger.warn("URLDecode失败", e);
}
return decoded;
}
public static void main(String [] args){
String str = "abcd{'a':'b'}";
String encoded = EncryptUtil.encodeUTF8StringBase64(str);
String decoded = EncryptUtil.decodeUTF8StringBase64(encoded);
System.out.println(str);
System.out.println(encoded);
System.out.println(decoded);
String url = "== wo";
String urlEncoded = EncryptUtil.encodeURL(url);
String urlDecoded = EncryptUtil.decodeURL(urlEncoded);
System.out.println(url);
System.out.println(urlEncoded);
System.out.println(urlDecoded);
}
}
头信息对象
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO {
/**
* 用户id
*/
private String id;
/**
* 用户名
*/
private String username;
/**
* 手机号
*/
private String mobile;
/**
* 姓名
*/
private String fullname;
}
@RestController
public class OrderController {
@GetMapping(value = "/r1")
@PreAuthorize("hasAuthority('p1')")//拥有p1权限方可访问此url
public String r1(){
//获取用户身份信息
UserDTO userDTO = (UserDTO) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return userDTO.getFullname()+"访问资源1";
}
}
1:修改UserDetails对象
2:转成json对象,放到令牌中
@Component
public class SimpleAuthenticationInterceptor implements HandlerInterceptor {
//访问资源前拦截
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//在这个方法中校验用户请求的url是否在用户的权限范围内
//取出用户的身份的信息
Object attribute = request.getSession().getAttribute(UserDto.SESSION_USER_KEY);
if (attribute == null){
//没有认证,提示登录
writeContent(response,"请登录");
}
UserDto userDto = (UserDto) attribute;
//请求的url
String requestURI = request.getRequestURI();
//用户权限匹配
if (userDto.getAuthorities().contains("p1") && requestURI.contains("r/r1")){
return true;
}
if (userDto.getAuthorities().contains("p2") && requestURI.contains("r/r2")){
return true;
}
writeContent(response,"没有权限,拒绝访问");
return false;
}
//响应信息给客户端的方法
private void writeContent(HttpServletResponse response, String msg) throws IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.print(msg);
writer.close();
response.flushBuffer();//清缓存
}
}
@RequestMapping(value = "/login",produces = "text/plaint;charset=utf-8")//文本格式
public String login(AuthenticationRequest authenticationRequest, HttpSession session){
//authenticationRequest客户端输入的账户+密码
UserDto userDto = authenticationService.authentication(authenticationRequest);
//存入session(服务端内存中)
session.setAttribute(UserDto.SESSION_USER_KEY,userDto);
return userDto.getFullname()+"登录成功";
}
@GetMapping(value = "/logout",produces = "text/plaint;charset=utf-8")
public String logout(HttpSession session){
session.invalidate();//清空session
return "退出成功";
}
@RequestMapping(value = "/r/r1",produces = "text/plaint;charset=utf-8")
@PreAuthorize("hasAnyAuthority('p1')")//拥有p1权限才可访问
public String r1(){
return "资源r1访问 +1";
}
通过单独的UAA认证服务进行认证和安全拦截
所有的登录认证请求必须经过此服务器—>令牌获取AuthorizationServer
远程资源拦截机制
网关将uaa认证服务的令牌进行发送
WebSecurityConfigurerAdapter网络安全适配器:放行所有请求
ResourceServerConfigurerAdapter资源服务配置器:有哪些微服务资源—>用户需要什么样的权限才能使网关发配token
ZuulConfig网关配置:允许跨域+优先级配置
ZuulFilter网关过滤器:通过过滤器对token令牌进行明文分配—>最后才能访问微服务
#/uaa/**开头的url通过uaa-service进行调用
zuul.routes.uaa-service.stripPrefix = false
zuul.routes.uaa-service.path = /uaa/**
zuul.routes.order-service.stripPrefix = false
zuul.routes.order-service.path = /order/**
各个微服务资源如何获取令牌进行认证和授权
OncePerRequestFilter【每个请求过滤一次】
ResourceServerConfigurerAdapter资源服务适配器:进行令牌认证+授权
@RestController
public class OrderController {
@GetMapping(value = "/r1")
@PreAuthorize("hasAuthority('p1')")//拥有p1权限方可访问此url
public String r1(){
//获取用户身份信息
UserDTO userDTO = (UserDTO) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return userDTO.getFullname()+"访问资源1";
}
}
用于分布式系统—>只登陆一次就可以访问所有有权限的服务,不需要每个系统都做一套认证系统
制作一套认证系统—>将认证从各个功能中抽离出来
利用Redis存储用户的身份
访问所有系统—>认证系统—>从Redis中查找token
Java能实现单点登录的认证框架:
grant_type:密码模式授权填password
username:账号
password:密码
POST请求:http://localhost:8080/auth/oauth/token
AUU认证服务接收到申请令牌的请求---->调用UserDetailsService接口—>将信息放入token中
GET: http://localhost:40400/auth/oauth/check_token?token 【校验令牌】
登录时:密码模式授权
访问资源时:校验令牌
令牌过期时:刷新令牌
Header
包括令牌的类型+哈希算法
{
"alg": "HS256",
"typ": "JWT"
}
Payload负载
存放有效信息类容
{
"sub": "1234567890",
"name": "456",
"admin": true
}
Signature签名
防止jwt内容被篡改该
使用Base64Url编码
header+"."+payload—>数字签名
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)//secret唯一标识
密钥证书:公钥+私钥
JDK生成密钥证书:
keytool -genkeypair -alias carterkey -keyalg RSA -keypass root -keystore carter.keystore -storepass rootstore
Keytool 是一个java提供的证书管理工具
-alias:密钥的别名
-keyalg:使用的hash算法
-keypass:密钥的访问密码
-keystore:密钥库文件名,xc.keystore保存了生成的证书
-storepass:密钥库的访问密码
keytool -list -keystore carter.keystore
利用证书—>私钥+内容—>jwt
公钥错误—>生成JWT令牌错误(spring security已经做)
//读取过期时间,已过期返回‐2
Long expire = stringRedisTemplate.getExpire(key);
在application.yml中配置参数
auth:
tokenValiditySeconds: 1200 #token存储到redis的过期时间
clientId: XcWebApp #客户端id
clientSecret: XcWebApp #客户端密码
cookieDomain: localhost #域名
cookieMaxAge: ‐1 #浏览器关闭cookie无效
【AUU认证服务】里面集成了spring security
userLogin方法用来申请令牌(账号+密码+客户端id)
/userLogin
参数:
账号username和密码password
client ID是yaml文件中的
作用: 将令牌存储到cookie
cookie:【response,域名,根路径,name:uid,token,cookie有效期,httpOnly:false浏览器可访问cookie】
域名:carter.com—指向—>localhost:8080 相当网关的反向代理 nginx技术
客户端id—>yaml中存取
将令牌存到cookie中
//认证方法
public AuthToken login(String username,String password,String clientId,String clientSecret){
//申请令牌
AuthToken authToken = applyToken(username,password,clientId, clientSecret);
if(authToken == null){
ExceptionCast.cast(AuthCode.AUTH_LOGIN_APPLYTOKEN_FAIL);
}
//将 token存储到redis
String access_token = authToken.getAccess_token();
String content = JSON.toJSONString(authToken);
boolean saveTokenResult = saveToken(access_token, content, tokenValiditySeconds);
if(!saveTokenResult){
}
利用POST请求仿用户登录
问题:请求被spring security拦截了
web.ignoring().antMatchers("/userLogin","/userLogout","/userjwt"); //spring security放行请求
Header
包括令牌的类型+哈希算法
{
"alg": "HS256",
"typ": "JWT"
}
Payload负载
存放有效信息类容
{
"sub": "1234567890",
"name": "456",
"admin": true
}
Signature签名
防止jwt内容被篡改该
使用Base64Url编码
header+"."+payload—>数字签名
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)//secret唯一标识
密钥证书:公钥+私钥
JDK生成密钥证书:
keytool -genkeypair -alias carterkey -keyalg RSA -keypass root -keystore carter.keystore -storepass rootstore
Keytool 是一个java提供的证书管理工具
-alias:密钥的别名
-keyalg:使用的hash算法
-keypass:密钥的访问密码
-keystore:密钥库文件名,xc.keystore保存了生成的证书
-storepass:密钥库的访问密码
keytool -list -keystore carter.keystore
利用证书—>私钥+内容—>jwt
公钥错误—>生成JWT令牌错误(spring security已经做)
[外链图片转存中…(img-eakJQmpw-1604028272894)]
//读取过期时间,已过期返回‐2
Long expire = stringRedisTemplate.getExpire(key);
[外链图片转存中…(img-vfLjLN8s-1604028272896)]
在application.yml中配置参数
auth:
tokenValiditySeconds: 1200 #token存储到redis的过期时间
clientId: XcWebApp #客户端id
clientSecret: XcWebApp #客户端密码
cookieDomain: localhost #域名
cookieMaxAge: ‐1 #浏览器关闭cookie无效
【AUU认证服务】里面集成了spring security
userLogin方法用来申请令牌(账号+密码+客户端id)
/userLogin
参数:
账号username和密码password
client ID是yaml文件中的
作用: 将令牌存储到cookie
cookie:【response,域名,根路径,name:uid,token,cookie有效期,httpOnly:false浏览器可访问cookie】
域名:carter.com—指向—>localhost:8080 相当网关的反向代理 nginx技术
客户端id—>yaml中存取
将令牌存到cookie中
//认证方法
public AuthToken login(String username,String password,String clientId,String clientSecret){
//申请令牌
AuthToken authToken = applyToken(username,password,clientId, clientSecret);
if(authToken == null){
ExceptionCast.cast(AuthCode.AUTH_LOGIN_APPLYTOKEN_FAIL);
}
//将 token存储到redis
String access_token = authToken.getAccess_token();
String content = JSON.toJSONString(authToken);
boolean saveTokenResult = saveToken(access_token, content, tokenValiditySeconds);
if(!saveTokenResult){
}
利用POST请求仿用户登录
问题:请求被spring security拦截了
web.ignoring().antMatchers("/userLogin","/userLogout","/userjwt"); //spring security放行请求