SpringSecurity
提供了一套Web应用安全性的完整解决方案
用户认证
验证某个用户是否为系统中的合法主体,通俗意义上为系统验证用户是否能够登录
用户授权
验证某个用户是否有权限执行某个操作,在一个系统中,不同用户所就有的权限是不同的
特点
- 与spring结合性好
- 全面的权限控制
- 专门为web开发而设计
- 重量级(需要引入较多的依赖)
Demo
- 导入spring security依赖
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.5.3</version>
</dependency>
- 编写controller
@Controller
public class logincontroller {
@GetMapping("/login")
public String login() {
return "login";
}
正常会跳转至login页面,但是添加了spring security后,会跳转至验证界面,默认用户名为User,密码为控制台随机生成的密码
原理
实质上是一个过滤器链,重点有三个过滤器
FilterSecurityInterceptor
一个方法级的权限过滤器,位于过滤链的底端,控制方法是否能被访问
ExceptionTranslationFilter
异常过滤器,用于处理在认证授权过程中抛出的异常
UsernamePasswordAuthenticationFilter
对/login的post请求进行拦截,校验表单中的用户名和密码
加载过程
调用doFilter中的初始化方法,获取FilterChainProxy,并通过FilterChainProxy遍历加载器,放在集合中返回
接口
UserDetailsService
没有配置时,账号密码是由Spring security自定义生成的,为了满足业务需求,我们需要自定义逻辑控制认证逻辑
PasswordEncoder
数据加密接口,一般用于密码加密
Web权限方案
认证
先查询配置文件-->查询配置类-->查询实现了UserDetailsService接口的实现类
-
通过配置文件设置
spring.security.user.name=??? spring.security.user.password=???
-
通过配置类实现
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter{ @Override protected void configure(AuthenticationManageBuilder auth) throws Exception{ //加密 BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); String password = passwordEncoder.encode("123"); auth.inMemoryAuthentication().withUser("chenchenchen").password(password).roles("admin"); } //如果不创建此对象,会报错,找不到对应的对象 @Bean PasswordEncoder password(){ return new BCryptPasswordEncoder(); } }
-
自定义实现类
- 创建配置类,设置使用哪个userDetailsService实现类
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter{ @Autowired private UserDetailService userDetailService; @Override protected void configure(AuthenticationManageBuilder auth) throws Exception{ //加密 auth.userDetailsService(userDetailsService).passwordEncoder(password()); } //如果不创建此对象,会报错,找不到对应的对象 @Bean PasswordEncoder password(){ return new BCryptPasswordEncoder(); } }
- 编写实现类,返回User对象
@Service public class MyUserDetailsService implements UserDetailsService{ @Override public UserDetails loadUserByUsername(String s) throw UsernameNotFoundException{ List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role"); return new User("chenchenchen",new BCryptPasswordEncoder().encode("123456"),auths)//auths为角色 } }
权限
- 创建配置类
- 在配置类中设置拦截内容/在controller上使用注解设置权限
自动登录
实现
-
创建用户表
CREATE TABLE `persistent_logins` ( `username` varchar(64) NOT NULL, `series` varchar(64) NOT NULL, `token` varchar(64) NOT NULL, `last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`series`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
配置类,注入数据源
@Configuration public class BrowserSecurityConfig { @Autowired private DataSource dataSource; @Bean public PersistentTokenRepository persistentTokenRepository(){ JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); // 赋值数据源 jdbcTokenRepository.setDataSource(dataSource); // 自动创建表,第一次执行会创建,如果表存在则应当删除 jdbcTokenRepository.setCreateTableOnStartup(true); return jdbcTokenRepository; } }
Swagger
- 世界上最流行的Api框架
- 前后端交流的工具
- 即时生成Api文档
- 可在线测试接口
Spring Boot整合Swagger
-
导入相应依赖包
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>
-
编写配置类
@Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket docket(){ return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .enable(true) //是否激活swagger .groupName("陈晨橙") //分组,可以设置多个docket .select() .apis(RequestHandlerSelectors.basePackage("com.shaem.swagger.controller")) //定义扫描的包 //拦截除了shaem以外的接口 .paths(PathSelectors.ant("/shaem/**")) .build(); } public ApiInfo apiInfo(){ return new ApiInfoBuilder() .title("陈晨橙") .description("test") .version("1.0") .termsOfServiceUrl("Url") .contact(new Contact("name","url","email")) .build(); } }
-
重启项目,访问测试 http://localhost:8080/swagger-ui.html
配置API分组
- 默认为default,通过groupName()可以配置分组
- 可以通过配置多个Docket达到分组效果
@Bean
public Docket docket1(){
return new Docket(DocumentationType.SWAGGER_2).groupName("group1");
}
@Bean
public Docket docket2(){
return new Docket(DocumentationType.SWAGGER_2).groupName("group2");
}
@Bean
public Docket docket3(){
return new Docket(DocumentationType.SWAGGER_2).groupName("group3");
}
实体类配置
- 使用
ApiModel
标识实体类 - 使用
ApiModelProperty
标识属性 - 只要请求接口返回值存在实体类,即会出现在文档中
@ApiModel("用户实体")
public class User {
@ApiModelProperty("用户名")
public String username;
@ApiModelProperty("密码")
public String password;
}
@RequestMapping("/getUser")
public User getUser(){
return new User();
}
常用注解
下面列一些经常用到的,未列举出来的可以另行查阅说明:
Swagger注解 | 简单说明 |
---|---|
@Api(tags = "xxx模块说明") | 作用在模块类上 |
@ApiOperation("xxx接口说明") | 作用在接口方法上 |
@ApiModel("xxxPOJO说明") | 作用在模型类上:如VO、BO |
@ApiModelProperty(value = "xxx属性说明",hidden = true) | 作用在类方法和属性上,hidden设置为true可以隐藏该属性 |
@ApiParam("xxx参数说明") | 作用在参数、方法和字段上,类似@ApiModelProperty |
定时、异步与邮件
定时
spring提供两个接口让我们能实现定时执行任务的功能
- Task Executor接口
- Task Scheduler接口
在微服务中,我们通常使用注解来实现
- @EnableScheduling(添加在启动类上)
- @Scheduled(cron=“表达式”) //添加在想要执行的方法上
cron表达式
一、结构
corn从左到右(用空格隔开):秒 分 小时 月份中的日期 月份 星期中的日期 年份
二、各字段的含义
字段 | 允许值 | 允许的特殊字符 |
---|---|---|
秒(Seconds) | 0~59的整数 | , - * / 四个字符 |
分(Minutes) | 0~59的整数 | , - * / 四个字符 |
小时(Hours) | 0~23的整数 | , - * / 四个字符 |
日期(DayofMonth) | 1~31的整数(但是你需要考虑你月的天数) | ,- * ? / L W C 八个字符 |
月份(Month) | 1~12的整数或者 JAN-DEC | , - * / 四个字符 |
星期(DayofWeek) | 1~7的整数或者 SUN-SAT (1=SUN) | , - * ? / L C # 八个字符 |
年(可选,留空)(Year) | 1970~2099 | , - * / 四个字符 |
注意事项:
每一个域都使用数字,但还可以出现如下特殊字符,它们的含义是:
(1)✳:表示匹配该域的任意值。假如在Minutes域使用*, 即表示每分钟都会触发事件。
(2)?:只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用,如果使用表示不管星期几都会触发,实际上并不是这样。
(3)-:表示范围。例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次
(4)/:表示起始时间开始触发,然后每隔固定时间触发一次。例如在Minutes域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次.
(5),:表示列出枚举值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。
(6)L:表示最后,只能出现在DayofWeek和DayofMonth域。如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。
(7)W:表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份 。
(8)LW:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。
(9)#:用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三。
异步
发送邮件一般不能立刻发送,让用户等待会降低体验,这个时候就可以用异步来模拟同步效果,让后台执行发送邮件的功能,其中涉及到多线程
- 在启动类上添加
@EnableAsync
- 在方法上使用
@Async
- 测试
邮件
在spring boot中使用邮件服务
-
引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>
-
配置文件
spring.mail.username=1435455332@qq.com #授权码可以在邮箱中的账号中找到 spring.mail.password=qq授权码 spring.mail.host=smtp.qq.com # qq需要配置ssl spring.mail.properties.mail.smtp.ssl.enable=true
-
测试
SimpleMailMessage message = new SimpleMailMessage(); message.setSubject("通知-今晚早点下班"); message.setText("今晚7:00下班"); message.setTo("1435455332@qq.com"); message.setFrom("1435455332@qq.com"); mailSender.send(message);
MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); helper.setSubject("通知"); helper.setText("<b style='color:red'>今天 6:30下班</b>",true); //发送附件 helper.addAttachment("1.jpg",new File("")); helper.addAttachment("2.jpg",new File("")); helper.setTo("1435455332@qq.com"); helper.setFrom("1435455332@qq.com"); mailSender.send(mimeMessage);