在web开发中,安全第一位! 过滤器,拦截器 大量原生代码 太冗余了
SpringSecurity简介:
Spring Security是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!
记住几个类:
Spring Security的两个主要目标是"认证"和授权(访问控制)
“认证”(Authentication) /ɔːˌθentɪˈkeɪʃn/ a 凡提
“授权”(Authorization) /ˌɔːθərəˈzeɪʃn/
这个概念是通用的,而不是只在Spring Security中存在
//AOP
@EnableWebSecurity
public class SecurityController extends WebSecurityConfigurerAdapter {
//授权
@Override
protected void configure(HttpSecurity http) throws Exception {
//首页所有人可以访问,功能页对应的人能访问
//请求授权的规则
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasRole("vip1")
.antMatchers("/level2/**").hasRole("vip2")
.antMatchers("/level3/**").hasRole("vip3");
//没有权限,默认到登陆页,可自定义前端接收url,username,password
http.formLogin().loginPage("/login");
//开启注销功能,注销成功去首页 http.csrf().disable();测试中,关闭了跨站请求防护,不然注销404
http.logout().logoutSuccessUrl("/");
//开启记住我功能 cookie默认保存两周,自定义接收前端参数
http.rememberMe().rememberMeParameter("remember");
}
//认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//这些数据正常应该从数据库获取 auth.jdbcAuthentication(); passwordEncoder 加密密码 明文无效
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("zjt").password(new BCryptPasswordEncoder().encode("1")).roles("vip1")
.and()
.withUser("admin").password(new BCryptPasswordEncoder().encode("1")).roles("vip1","vip2")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("1")).roles("vip1","vip2","vip3");
}
}
/**
* Simple Quickstart application showing how to use Shiro's API.
* 简单的快速启动应用程序显示如何使用Shiro的API。
* @since 0.9 RC2
*/
public class Quickstart {
private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
public static void main(String[] args) {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
// 获取当前用户对象 Subject 安全工具.获得主题
Subject currentUser = SecurityUtils.getSubject();
// 通过当前用户拿到Session(shiro)
Session session = currentUser.getSession();
// 实现了存值取值
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("---> Retrieved the correct value! [" + value + "]");
}
// 判断当前的用户是否已经被认证, 即是否已经登录,调用 Subject 的 isAuthenticated()
if (!currentUser.isAuthenticated()) {
// 把用户名和密码封装为 UsernamePasswordToken 对象, 得到一个token(令牌)
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
// 设置记住我 rememberme
token.setRememberMe(true);
try {
// 执行登录.
currentUser.login(token);
}
// 若没有指定的账户
catch (UnknownAccountException uae) {
log.info("未知账户异常+令牌.委托人 " + token.getPrincipal());
return;
}
// 若账户存在,但密码不匹配
catch (IncorrectCredentialsException ice) {
log.info("错误的凭证异常+令牌.委托人 " + token.getPrincipal());
return;
}
// 用户被锁定的异常 LockedAccountException
catch (LockedAccountException lae) {
log.info("锁定账户异常+令牌.委托人 " + token.getPrincipal());
}
// 所有认证时异常的父类.
catch (AuthenticationException ae) {
}
}
//say who they are:
//print their identifying principal (in this case, a username):
log.info("----> User [" + currentUser.getPrincipal() + "] logged in successfully.");
//test a role: 测试是否有某一个角色. 调用 Subject 的 hasRole 方法.
if (currentUser.hasRole("schwartz")) {
log.info("----> May the Schwartz be with you!");
} else {
log.info("----> Hello, mere mortal.");
return;
}
// 测试用户是否具备某一个权限. 调用 Subject 的 isPermitted() 方法。
if (currentUser.isPermitted("lightsaber:weild")) {
log.info("----> You may use a lightsaber ring. Use it wisely.");
} else {
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}
// 测试用户是否具备某一个行为,更有力度的检测
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
log.info("你通过了更有力的检查");
} else {
log.info("Sorry, 你不被允许");
}
// 检测当前用户是否认证
System.out.println("---->" + currentUser.isAuthenticated());
// all done - log out! 全部完成-注销 调用 Subject 的 Logout() 方法.
currentUser.logout();
// 检测当前用户是否认证
System.out.println("---->" + currentUser.isAuthenticated());
// 退出系统
System.exit(0);
}
}
依赖:
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-springartifactId>
<version>1.5.1version>
dependency>
controller:
@Controller
public class MyController {
// 从前端接收用户和密码
@RequestMapping("/login")
public String login(String username, String password, Model model) {
// 获取当前的用户
Subject subject = SecurityUtils.getSubject();
// 封装用户的登陆数据
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token);// 执行登陆方法,如果没有异常就说明ok
return "index";
} catch (UnknownAccountException e) {
model.addAttribute("msg", "账号错误,未知用户异常");
return "login";
} catch (IncorrectCredentialsException e) {
model.addAttribute("msg", "密码错误,错误凭证异常");
return "login";
}
}
// 未授权用户给予提示
@RequestMapping("/noauth")
@ResponseBody
public String unauthorized() {
return "未经授权无法访问此页面";
}
}
shiroConfig:
@Configuration
public class ShiroConfig {
@Bean // 创建 Realm范围 对象,需要自定义类 : 步骤1
public UserRealm userRealm() {
return new UserRealm();
}
@Bean // DefaultWebSecurityManager 默认网站安全管理 : 步骤2
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 关联 Realm
securityManager.setRealm(userRealm);
return securityManager;
}
@Bean // ShiroFilterFactoryBean shiro过滤器工厂bean : 步骤3
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
// 添加shiro内置过滤器
/*
* anon : 无需认证就可以访问
* authc: 必须认证才能访问
* user : 必须拥有记住我功能,才能访问
* perms: 拥有某个资源的权限才能访问
* role : 拥有某个角色才可访问
*/
// 拦截,认证,验证
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/add", "authc");
filterMap.put("/update", "authc");
// 授权,正常情况下,未经授权会跳转到未授权页面
filterMap.put("/add", "perms[user:add]");
filterMap.put("/update", "perms[user:update]");
bean.setFilterChainDefinitionMap(filterMap);
// 设置登陆的请求
bean.setLoginUrl("/toLogin");
// 设置未授权路径
bean.setUnauthorizedUrl("/noauth");
return bean;
}
}
自定义realm:
// 自定义授权范围 AuthorizingRealm
public class UserRealm extends AuthorizingRealm {
@Autowired
IUserService service;
// 认证,验证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("执行了认证");
// 调用 MyController 的 UsernamePasswordToken token = new UsernamePasswordToken(username, password);
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
// 假设输入的密码跟数据库的不一样
User user = service.queryUserByName(userToken.getUsername());
if (user == null) {
return null; // 自动抛出 UnknownAccountException 未知用户异常
}
// 密码认证 shiro做了加密 默认 SimpleCredentialsMatcher 简单密码加密
// 还可以MD5 e10adc3949ba59abbe56e057f20f883e MD5盐值加密
return new SimpleAuthenticationInfo(user, user.getPwd(), "");
}
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("执行了授权");
// 固定代码,简单验证信息()
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 拿到当前登录的这个对象
Subject subject = SecurityUtils.getSubject();
// 获取 return new SimpleAuthenticationInfo(user,user.getPwd(),"");中的 Principal:user对象
User currentUser = (User) subject.getPrincipal();
// 设置当前用户权限:增加字符串许可
info.addStringPermission(currentUser.getPerms());
return info;
}
}
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.9.2version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>2.9.2version>
dependency>
@Configuration
@EnableSwagger2 //开启Swagger2
public class SwaggerConfig {}
5.测试运行:http://localhost:8080/swagger-ui.html
Swagger的bean实例 Docket(摘要)
//配置 Docket 的bean实例
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(true) //默认为true 如果为false 则Swagger在浏览器不能访问
.groupName("君不妄")
.select()
.apis(RequestHandlerSelectors.basePackage("com.zjt.controller"))
.build();
}
//配置swagger信息=apiInfo
private ApiInfo apiInfo(){
//作者信息 name,url,email
Contact CONTACT = new Contact("zjt", "www.123.com", "[email protected]");
return new ApiInfo(
"title:标题API",
"description:Api文档",
"version:2.0",
"termsOfServiceUrl:服务条款网址",
CONTACT,
"license:许可 Apache 2.0",
"licenseUrl: 许可链接http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList());
}
配置扫描的接口
//配置 Docket 的bean实例
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
/*配置要扫描接口的方式
basePackage():指定扫描的包
any():扫描全部
none():不扫描的
withClassAnnotation():扫描类上的注解,参数是一个注解的反射对象
withMethodAnnotation():扫描方法上的注解
*/
.apis(RequestHandlerSelectors.basePackage("com.zjt.controller"))
//paths 过滤条件
.paths(PathSelectors.ant("/zjt/**"))
.build();
}
配置是否启动Swagger
//配置 Docket 的bean实例
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(false) //默认为true 如果为false 则Swagger在浏览器不能访问
.select()
.apis(RequestHandlerSelectors.basePackage("com.zjt.controller"))
.build();
}
配置API文档的分组
.groupName("君不妄")
如何配置多个分组,多个Docket实例即可
@Bean
public Docket docket1(){
return new Docket(DocumentationType.SWAGGER_2).groupName("A");
}
@Bean
public Docket docket2(){
return new Docket(DocumentationType.SWAGGER_2).groupName("B");
}
常用注解说明
详细地址
为避免后端任务调度时间过久,导致前端页面无法展示内容,陷入加载或转圈或白屏,造成不好的用户体验,可以使用开启异步方法
在springboot入口类添加注解
@EnableAsync
在需要持久化超时的方法上
@Async
生成邮箱授权码:
配置application.properties
spring.mail.username=11567*****@qq.com
# 授权码
spring.mail.password=rvpijmeuycizfhjh
spring.mail.host=smtp.qq.com
# qq独有 开启加密验证
spring.mail.properties.mail.smtp.ssl.enable=true
代码实现:
//引入一个java邮件发送 实现类
@Autowired
JavaMailSenderImpl mailSender;
@Test
void test() {
//简单的邮件
SimpleMailMessage message = new SimpleMailMessage();
//正文
message.setSubject("君不妄"); //主题
message.setText("这是一封java测试邮件"); //内容
message.setTo("134****@qq.com"); //收件人
message.setFrom("1156****@qq.com"); //发件人
//邮件发送
mailSender.send(message);
}
@Test
void test2() throws MessagingException {
//一个复杂的邮件
MimeMessage mimeMessage = mailSender.createMimeMessage();
//组装 开启多文件 multipart:true
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true);
//正文
helper.setSubject("这是一个测试主题");
// 开启html功能 html:true
helper.setText("这是一封java测试邮件
",true);
helper.setTo("1156****@qq.com");
helper.setFrom("11*****@qq.com");
//添加一个附件
helper.addAttachment("1.jpg",new File("C:\\Users\\Administrator\\Desktop\\鬼刀 (48).jpg"));
helper.addAttachment("2.jpg",new File("C:\\Users\\Administrator\\Desktop\\鬼刀 (49).jpg"));
//邮件发送
mailSender.send(mimeMessage);
}
@EnableScheduling 开启时程安排 : 在springboot入口类开启定时功能
@Scheduled 预定的: 什么时候执行
/*在一个特定时间执行这个方法
cron 表达式 秒 分 时 日 月 周几
"10 10 12 L * ?" 没有最后一天L 的12点,10分,10秒执行一次
"10 0/5 12,14 * * ?" 每天的12和14点,每隔5分钟,10秒执行一次
"0 0 0 27 10 ? *" 我的生日的零点祝福
cron:计时程序
*/
@Scheduled(cron = "0 0 0 27 10 ? *")
public void hello(){
System.out.println("生日快乐");
在《分布式系统原理与范型》一书中有如下定义:”分布式系统是若干独立计算机的集合, 这些计算机对于用户来说就像单个相关系统"
分布式系统是由一-组通过网络进行通信、 为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。其目的是利用更多的机器,处理更多的数据。
分布式系统(distributed system)是建立在网络之上的软件系统。
首先需要明确的是,只有当单个节点的处理能力无法满足日益增长的计算、存储任务的时候,且硬件的提升(加内存、加磁盘、使用更好的CPU)高昂到得不偿失的时候,应用程序也不能进一步优化的时候,我们才需要考虑分布式系统。因为,分布式系统要解决的问题本身就是和单机系统一样的, 而由于分布式系统多节点、通过网络通信的拓扑结构,会引入很多单机系统没有的问题,为了解决这些问题又会引入更多的机制、协议,带来更多的问题。。。
Apache Dubbo |是一款高性能、轻量级的开源Java;RPC框架, 它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
Dubbo是一个分布式服务框架,RPC远程服务调用方案(RPC 解决通讯和序列化),以及SOA服务治理方案。简单的说,dubbo就是个服务框架,如果没有分布式的需求,其实是不需要用的,只有在分布式的时候,才有dubbo这样的分布式服务框架的需求,并且本质上是个服务调用的东东,说白了就是个远程服务调用的分布式框架
Provider: 暴露服务的服务提供方。
Consumer: 调用远程服务的服务消费方。
Registry: 服务注册与发现的注册中心。
Monitor: 统计服务的调用次调和调用时间的监控中心。
Container: 服务运行容器。
默认端口节点:2181
ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
是一个监控管理后台~查看我们注册了那些服务,管理调优
前提: zookeeper服务已开启!
提供者提供服务
导入依赖
配置注册中心的地址,以及服务发现名,和要扫描的包
# 服务应用name
dubbo.application.name=provider-service
# 注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
# 要被注册服务的包路径
dubbo.scan.base-packages=com.zjt.service
import org.apache.dubbo.config.annotation.Service;
import org.springframework.stereotype.Component;
//zookeeper:服务注册与发现
@Service //可以被扫描到,项目一启动就自动注册到注册中心
@Component //使用了dubbo之后尽量不适用@Service注解,重名
public class FuwuServiceImpl implements FuwuService {
@Override
public String test() {
return "服务 提供者 ";
}
}
消费者如何消费
Dubbo实现服务调用是通过RPC的方式,即客户端和服务端共用一个接口(将接口打成一个jar包,在客户端和服务端引入这个jar包),客户端面向接口写调用,服务端面向接口写实现,中间的网络通信交给框架去实现
三层架构 + MVC
架构 —> 解耦
开发框架Spring IOC AOP
IOC :控制反转
租房子以前需要自己一个一个跑,现在找中介,他那有房源,让中介帮我们完成租房子,原来我们都是自己一步步操作,现在交给容器了!我们需要什么就去拿就可以了
AOP :切面(本质,动态代理)
为了解决什么?不影响业务本来的情况下,实现动态增加功能,大量应用在日志,事务…等等方面
Spring是一个轻量级的J ava开源框架,容器
目的:解决企业开发的复杂性问题
Spring是春天,觉得他是春天,也十分复杂,配置文件!
SpringBoot
SpringBoot并不是新东西,就是Spring的升级版!
新一代JavaEE的开发标准,开箱即用! ->拿过来就可以用! 它自动帮我们配置了非常多的东西,我们拿来即用!
特性:约定大于配置!
随着公司体系越来越大,用户越来越多!
微服务架构—>新架构
模块化,功能化! 用户,支付,签到,娱乐,… ;
人多余多:一台服务器解决不了;在增加服务器 ! 横向
假设A服务器占用98%资源,B服务器只占用了10% 。一 负载均衡;
将原来的整体项目,分成模块化,用户就是一个单独的项目,签到也是一个单独的项目,项目和项目之前需要通信,如何通信?
用户非常多,而签到十分少! 给用户多一点服务器,给签到少一点服务器!
微服务架构问题?
分布式架构会遇到的四个核心问题?
解决方案:
SpringCloud,是一套生态,就是来解决以上分布式架构的4个问题
想使用SpringCloud,必须要掌握SpringBoot,因为SpringCloud是基于SpringBoot;
Spring Cloud NetFlix ,出来了一套解决方案! 一站式解决方案。我们都可以直接去这里拿?
(1). Api网关,zuu1组件
(2). Feign --> HttpClient —> HTTP的通信方式,同步并阻塞
(3). 服务注册与发现,Eureka
(4). 熔断机制,Hystrix
2018年年底,NetFlix宣布无限期停止维护。生态不再维护,就会脱节。
Apache Dubbo zookeeper,第二套解决系统
(1). API :没有! 要么找第三方组件,要么自己实现
(2). Dubbo是一个高性能的基于Java实现的RPC通信框架! 2.6.x
(3). 服务注册与发现,zookeeper: 动物园管理者(Hadoop ,Hive)
(4). 没有: 借助了Hystrix
不完善,Dubbo 他们3.0融合了阿里的长处 推出后可能很强
SpringCloud Alibaba一站式解决方案 !还没成熟
目前,又提出了一种方案:
服务网格:下一代微服务标准,Service Mesh
代表解决方案: istio (你们未来可能需要掌握! )
为什么要解决这个问题? 本质:网络是不可靠的!