Spring Security
多个过滤器来实现所有安全的功能,只需要注册一个特殊的DelegatingFilterProxy过滤器到WebAppliationInitializer即可
实际使用中需要让自己的Initializer类继承AbstractSecurity WebApplicationInitializer抽象类即可。
AbstractSecurityWebApplicationInitializer实现了WebApplicationInitializer接口,并通过onStartup方法调用
①、依赖
spring-boot-starter-data-jpa
spring-boot-starter-security
spring-boot-starter-thymeleaf
ojdbc6
thymeleaf-extras-springsecurity4
②、配置
bash
spring.datasource.drivuerClassName=oracle.jdbc.OracleDriver
spring.datasource.url=jdbc\:oracle\:thin\:@localhost\:1521\:xe
spring.datasource.username=boot
spring.datasource.password=boot
logging.level.org.springframework.security=INFO
spring.thymeleaf.cache=false
spring.jpa.hibernate.ddl-auto=update #自动生成用户表、角色表以及关联表
spring.jpa.show-sql=true
将bootstrap.min.css放置在src/main/resources/static/css下,此路径默认不拦截
③、用户和角色
java
@Entity
public class SysUser implements UserDetails{//实现该接口,用户实体即为Spring Security使用的用户
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private Long id;
private String username;
private String password;
@ManyToManay(cascade = {CascadeType.REFRESH},fetch=FetchType.EAGER)
private List<SysRole> roles;
@Override
public Colletion<? extends GrantedAuthority> getAuthorities(){//将用户的角色作为权限
List<GrantedAuthority> auths = new ArrayList<>();
List<SysRole> roles = this.getRoles();
for(SysRole role:roles){
auths.add(new SimpleGrantedAuthority(role.getName()));
}
return false;
}
@Override
public boolean isAccountNonExpired(){
return true;
}
@Override
public boolean isAccountNonLocked(){
return true;
}
@Override
public boolean isCredentialsNonExpired(){
return true;
}
@Override
public boolean isEnabled(){
return true;
}
//省略get\set方法
}
java
@Entity
public class SysRole{
@Id
@GeneratedValue
private Long id;
private String name;
//省略getter、setter方法
}
④、Dao数据访问
java
public interface SysUserRepository extends JpaRepository<SysUser,Long>{
SysUser findByUsername(String username);
}
⑤、自定义Service服务
java
public class CustomUserService implements UserDetailsService{//自定义需要实现该接口
@Autowired
SysUserRepository userRepository;
@Override
public UserDetails loadUserByUsername(){//获得用户,直接返回给SprngSecurity
SysUser user = userRepository.findUsername(username);
if(user == null){
throw new UsernameNotFoundException("用户名不存在");
}
return user;
}
}
⑥、Configuration配置SpringMVC
注册访问/login转向login.html
java
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter{
//访问http://localhost:8080 将会自动跳转到登录页面
@Override
public void addViewController(ViewControllerRegistry registry){
registry.addViewController("/login").setViewName("login");
}
}
扩展Spring Security配置,所有请求需要认证登录后才能方法(登录和注销)
SpringBoot针对Spring Security的自动配置通过SecurityAutoConfiguration和SecurityProperties来配置
SpringBoot做了很多配置,需要扩展配置只需要继承WebSecurityConfigurerAdapter类,无需@EnableWebSecurity注解
java
//相关的安全配置
@Configuration
//@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
@Bean
UserDetailsService customUserService(){//注册该bean
return new CustomUserService();
}
/**
用户认证
①、添加内存中的用户,并且可以给用户指定角色权限
auth.inMemoryAuthentication().withUser("wyf").password("wyf")
.roles("ROLE_ADMIN").and().withUser("wisely").password("wisely").roles("ROLE_USER");
②、JDBC中的用户直接指定dataSource即可
这里的SpringSecurity默认了数据库接口,通过jdbcAuthentication源码可以得出JdbcDaoImpl中定义了默认的用户及角色权限
@Autowired
DataSource dataSource;
@Override
protected void configure(AuthenticationManagerBuilder auth)throws Exception{
auth.jdbcAuthentication().dataource(dataSource);
//也可以i定义查询用户和权限的SQL
auth.jdbcAuthentication().dataource(dataSource)
.usersByUsernameQuery("select username,password,true from myusers where username=?")
.authoritiesByUsername("select username,role from roles where username=?");
}
*/
//③、通用用户,实现UerDetailsService接口,以上JDBC用户和内存用户就是其实现
@Override
protected void configure(AuthenticationManagerBuilder auth)throws Exception{
auth.userDetailsService(customUserServie());//添加自定义认证,注册
}
/**
请求授权,匹配了一下请求路径,需要针对当前用户的信息对请求路径进行安全处理
Ⅰ、antMatchers 使用Ant风格的路径皮喷
Ⅱ、regexMatchers 使用正则表达式匹配路径
Ⅲ、anyRequest 匹配所有路径
安全处理方法:
access(String) SpringEL表达式结果为true时可访问
anonymous() 匿名访问
denyAll() 用户不能访问
fullyAuthenticated() 用户完全认证可访问(非remeber me下自动登录)
hasAnyAuthority(String..) 如果用户有参数,则其中任一权限可访问
hasAnyRole(String..) 如果用户有参数,则其中任一角色可访问
hasAuthority(String) 如果用户有参数,则其权限可访问
hasIpAddress(String) 如果用户来自参数中的IP则可访问
hasRole(String) 若用户有参数中的角色可访问
permitAll() 用户可任意访问
rememberMe() 运行通过remember-me登录的用户访问
authenticated() 用户登录后可访问
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ROLE_ADMIN")
.antMatchers("/user/**").hasAnyRole("ROLE_ADMIN","ROLE_USER")
.anyRequst().authenticated(); //其余所有请求都需要认证登录后才可以访问
*/
@Override
protected void configure(HttpSecurity http) throws Exception{
http.authorizeRequests()//开始请求权限配置
.anyRequest().authenticated()//所有请求需要认证登陆后才能访问
.and()
.formLogin() //定制登录操作
.loginPage("/login")//登录页面的访问地址
.defaultSuccessUrl("/index")//指定登录成功后转向的页面
.failureUrl("/login?error")//登录失败转向的页面
.permitAll()//定制登录行为,登录页面可以任意访问
.and()
.rememberMe()//开启cookie存储用户信息
.tokenValiditySeconds(1209600)//指定cookie有效期2个星期
.key("myKey")//指定cookie中的私钥
.and()
.logout()
.logout("/costom-logout")//指定注销的URL路径
.logoutSuccessUrl("/logout-success")//指定注销成功后转向的页面
.permitAll();//定制注销行为,注销请求可任意访问
}
}
⑦、页面
html
<!--Thymeleaf提供了Spring Security的标签支持-->
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
获得当前用户名
sec:authentication="name"
当前用户觉得为ROLE_ADMIN时才现实标签内容
sec:authorize="hasRole('ROLE_ADMIN')"
注销的默认路径/logout,需要通过POST请求提交
th:action="@{/logout}" method="post"
Spring Batch
处理大量数据的框架,用来读取大量数据,然后进行一定处理后输出成指定的形式
①、依赖
spring-boot-starter-jdbc
spring-boot-starter-batch(因为使用Oracle,所以排除hsqldb)
spring-boot-starter-web
ojdbc6
hibernate-validator 数据校验
数据准备:
src/main/resources/people.csv中添加 jordan,23,非汉族,芝加哥 等多条类似的数据
数据表sql
id name age nation address
②、实体类
java
public class Person{
@Size(max=4,min=2) //使用JSR-303校验数据
private String name;
private int age;
private String nation;
private String address;
//省略get和set方法
}
③、数据处理及校验
java
public class CsvIntemProcessor extemds ValidatingItemProcessor<Person>{
@Override
public Person process(Person item) throws ValidationException{
super.process(item);//调用自定义校验器
if(item.getNation().equals("汉族")){
item.setNation("01");
}else{
item.setNation("02");
}
return item;
}
}
java
public class CsvBeanValidator<T> implements Validator<T>,InitializingBean{
private javax.validation.Validator validator;
@Override
public void afterProperitesSet()throws Exception{//使用JSR-303校验数据
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
validator = validatorFactory.usingContext().getValidator();
}
@Override
public void validate(T value)throws ValidationException{
//使用Validator的validate的方法校验数据
Set<ConstraintViolation<T>> constraintViolations = validator.validate(value);
if(constraitViolations.size()>0){
StringBuilder message = new StringBuilder();
for(ConstraintVilation<T> constraintViolation : constraintViolations){
message.append(constraintViolation.getMessage() + "\n");
}
throw new ValidationException(message.toString());
}
}
}
④、Job监听
java
public class CsvJobListener implements JobExecutionListener{
long startTime;
long endTime;
@Override
public void beforeJob(JobExecution jobExecution){
startTime = System.currentTimeMillis();
System.out.println("任务处理开始");
}
@Override
public void afterJod(JobExecution jobExecution){
startTime = System.currentTimeMillis();
System.out.println("任务处理结束");
System.out.println("耗时:"+(endTime-startTime)+"ms");
}
}
⑤、配置
- JobRepository 用来注册Job容器
- JobLauncher 用来启动Job的接口
- Job 实际执行的任务,包含一个或多个Step
- Step 包含ItemReader ItemProcessor和ItemWriter
- ItemReader 用来读取数据的接口
- ItemProcessor 用来处理数据的接口
- ItemWriter 用来输出数据的接口
java
@Configuration
@EnableBatchProcessing //开启批处理支持
public class CsvBatchConfig{
@Bean
public ItemReader<Person> reader()throws Exception{
FlatFileItemReader<Person> reader = new FlatFileItemReader<>();//读取文件
reader.setResource(new ClassPathResource("people.csv"));//设置csv文件路径
reader.setLineMapper(new DefautLineMapper<Person>(){
{
setLineTokenizer(new DelimitedLineTokenizer(){
{
setNames(new String[]{"name","age","nation","address"});
}
});
setFieldSetMapper(new BeanWrapperFieldSetMapper<Person>(){
{
setTargetType(Person.class);
}
});
}
});
return reader;
}
@Bean
public ItemProcessor<Person,Person> processor(){
CsvItemProcessor processor = new CsvItemProcessor();//使用自定义的ItemProcessor的实现CsvItemProcessor
processor.setValidator(csvBeanValidator());//为processor指定校验器为CsvBeanValidator
return processor;
}
@Bean
public ItemWriter<Person> write(DataSource dataSource){//SpringBoot让已有的容器bean,注入
JdbcBatchItemWriter<Person> writer = new JdbcBatchItemWriter<>();//使用JDBC批处理的jdbcBatchItemWriter来写数据到数据库
writer.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>());
String sql = "insert into person"+"(id,name,age,nation,address)"+"values(hibernate_sequence.nextval,:name,:age,:nation,:address)";
wirter.setSql(sql);//设置要执行批处理的SQL语句
writer.setDataSource(dataSource);
return writer;
}
@Bean
public JobRepository jobRepository(DataSource dataSource,PlatformTransactionManager transactionManager)throws Exception{
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JopRepositoryFactoryBean();
jobRepositoryFactoryBean.setDataSource(dataSource);
jobRepositoryFactoryBean.setTransactionManager(transactionManager);
jobRepositoryFactoryBean.setDatabaseType("oracle");
return jobRepositoryFactoryBean.getObject();
}
@Bean
public SimpleJobLauncher jobLauncher(DataSource dataSource,PlatformTransactionManager transactionManager){
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
jobLauncher.setJobRepository(jobRepository(dataSource,transactionManager));
return jobLauncher;
}
@Bean
public Job importJob(JobBuilderFactory jobs,Step s1){
return jobs.get("importJob")
.incrementer(new RunIdIncrementer())
.flow(s1)//为Job指定Step
.end()
.listener(csvJobListener())//绑定监听器
.build();
}
@Bean
public Step step1(StepBuilderFactory stepBuilderFactory,ItemReader<Person> reader,
ItemWriter<Person> writer,ItemProcessor<Person,Person> processor){
return stepBuilderFactory
.get("step1")
.<Person,Person>chunk(65000)//批处理,每次提交65000条数据
.reader(reader)//为step绑定那个reader
.processor(processor)//给step绑定processor
.writer(writer)//给step绑定writer
.build();
}
@Bean
public CsvJobListener csvJobListener(){
return new CsvJobListener();
}
@Bean
public Validator<Person> csvBeanValidator(){
return new CsvBeanValidator<Person>();
}
}
⑥、运行
SpringBoot会自动初始化SpringBatch数据库,并将CSV中的数据导入到数据库中
监听器效果
数据已导入且做转换处理
⑦、手动触发任务
注释调CsvBatchConfig类的@Configuration注解,让此类不在起效
新建TriggerBatchConfig配置类,内容同CsvBatchConfig完全保持一致
修改定义ItemReader
java
@Bean
@StepScope
public FlatFileItemReader<Person> reader(@Value("#{jobParameters['input.file.name']}") String pathToFile)throws Exception{
FlatFileItemReader<Person> reader = new FlatFileItemReader<>();//
reader.setResource(new ClassPathResource(pathToFile));//
reader.setLineMapper(new DefautLineMapper<Person>(){
{
setLineTokenizer(new DelimitedLineTokenizer(){
{
setNames(new String[]{"name","age","nation","address"});
}
});
setFieldSetMapper(new BeanWrapperFieldSetMapper<Person>(){
{
setTargetType(Person.class);
}
});
}
});
return reader;
}
控制定义
java
@RestController
public class DemoController{
@Autowired
JobLauncher jobLauncher;
@Autowired
Job importJob;
public JobParameters jobParameters;
@RequestMapping("/imp")
public String imp(String fileName)throws Exception{
String path = fileName + ".csv";
jobParameters = new JobParametersBuilder()
.addLong("time",System.currentTimeMillis())
.addString("input.file.name",path)
.toJobParameters();
jobLauncher.run(importJob,jobParameters);
return "ok";
}
}
此时如果关闭SpringBoot为我们自动执行了Job的配置,在application.properties里使用如下代码
spring.batch.job.enabled=false
访问http://localhost:8080/imp?fileName=people可获得相同的数据导入效果
异步消息
Spring通过@EnableJms @EnableRabbit开启支持
SpringBoot自动开启了@EnableJms @EnableRabbit
JMS
①、安装ActiveMQ
docker run -d -p 61616:61616 -p 8161:8161 cloudesire/activemq
其中61616是消息代理的端口
8161是ActiveMQ管理界面端口 http://localhost:8161打开ActiveMQ的管理界面 账号密码admin/admin
也可以将ActiveMQ内嵌在程序里依赖activemq-broker
②、依赖
spring-boot-starter
spring-jms
activemq-client
application.properties配置ActiveMQ消息代理
spring.activemq.broker-url=tcp://localhost:61616
③、消息定义
java
public class Msg implements MessageCreator{
@Override
public Message createMessage(Session session) throws JMSException{
return session.createTextMessage("测试消息");
}
}
④、消息发送及目的地
java
@SpringBootApplication
public class ChApplication implements CommandLineRunner{//SpringBoot提供的接口
@Autowired
JmsTemplate jmsTemplate;//注入SpringBoot配置好的bean
public static void main(String[] args){
SpringApplication.run(ChApplication.class,args);
}
@Override
public void run()throws Exception{
jmsTemplate.send("my-destination",new Msg());//向my-destination目的地发送Msg的消息
}
}
⑤、消息监听
java
@Component
public class Receiver{
@JmsListener(destination="my-destination")//Spring4.1新特性,监听的目的地,接收消息
public void receiveMesssage(String message){
Systemm.out.println("接收到:<" + message +">");
}
}
⑥、运行
AMQP
①、安装RabbitMQ
先下载安装erlang http://www.erlang.org/download.html
下载RabbitMQ https://www.rabbitmq.com/download.html
docker run -d -p 5672:5672 -p 15672:15672 rabbitmq:3-management
5672属于消息代理的端口
15672属于管理界面的端口 http://localhost:15672 guest/guest
②、依赖
spring-boot-starter-amqp
SpringBoot默认Rabbit注解为localhost、端口号5672
无需为SpringBoot的application.properties配置RabbitMQ的连接信息
③、发送信息及目的地
java
@SpringBootApplication
public class ChApplication implements CommandLineRunner{
@Autowired
RabbitTemplate rabbitTemplate;//注入SpringBoot配置好的
public static void main(String[] args){
SpringApplication.run(ChApplication.class,args);
}
@Bean
public Queue wiselyQueue(){
return new Queue("my-queue");//定义目的地队列
}
@Override
public void run(String... args)throws Exception{
//向队列my-queue发送消息
rabbitTemplate.convertAndSend("my-queue","来自RabbitMQ的问候");
}
}
④、消息监听
java
@Component
public class Receiver{
@RabbitListener(queues = "my-queue")
public void receiveMessage(String message){
System.out.println("Received<" + message + ">");
}
}
⑤、运行
系统集成Spring Integration
解决不同系统之间交互的问题,通过异步消息驱动来达到系统交互时系统之间的松耦合。
①、读取https://spring.io/blog.atom的新闻聚合文件,atom是一种xml文件,且格式是固定的
将读取到的消息通过分类(Category),将消息转到不同的消息通道
将分类为releases和engineering的消息写入磁盘,将分类为news的消息通过邮件发送
②、依赖
spring-boot-starter-integration
spring-boot-starter-mail
Spring Integration对atom及mail的支持:
spring-integration-feed
spring-integration-mail
②、读取流程(入口类完成)
java
@Value("https://spring.io/blog.atom")//获得资源
Resource resource
@Bean(name=PollerMetadata.DEFAULT_POLLER)
public PollerMetadata poller(){//配置默认的轮询方式
return Pollers.fixedRate(500).get();
}
@Bean
public FeedEntryMessageSource feedMessageSource() throws IOException{//
FeedEntryMessageSource messageSource = new FeedEntryMessageSource(resource.getURL(),"news");
return messageSource;
}
@Bean
public IntegrationFlow myFlow() throws IOException{
return IntegrationFlows.from(feedMessageSource())//流程从from 方法开始
.<SyndEntry,String> route(payload ->payload.getCategories().get(0).getname(),//选择路由,消息体payload类型SyndEntry
mapping ->mapping.channelMapping("releases","releasesChannel")
.channelMapping("engineering","engineeringChannel")//不同分类的值转向不同的消息通道
.channelMapping("news","newsChannel"))
.get();//获得IntegrationFlow实体,配置为Spring的Bean
}
③、release流程
java
@Bean
public IntegrationFlow releasesFlow(){
return IntegrationFlows.from(MessageChannels.queue("releasesChannel",10)) //从消息通道releasesChannel开始获取数据
.<SyndEntry,String> transform(payload->"《" + payload.getTitle()+"》"+payload.getLink()+getProperty("line.separator"))//数据转换
.handle(Files.outboundAdapter(new File("e:/springblog"))//file出站适配器
.fileExistsMode(FileExistsMode.APPEND)
.charset("UTF-8")
.fileNameGenerator(message -> "releases.txt")
.get())
.get();
}
④、engineering流程
java
@Bean
pubic IntegratonFlow engineeringFlow(){
return IntegrationFlows.from(MessageChannels.queue("engineeringChannel",10))
.<SyndEntry,String> transform(e->"《"+e.getTitle()+"》"+e.getLink()+getProperty("line.separator"))
.handle(Files.outboundAdapter(new File("e:/springblog"))
.fileExistsMode(FileExistsMode.APPEND).charset("UTF-8")
.fileNameGenerator(message->"engineering.txt").get())
.get();
}
⑤、news流程
java
@Bean
public IntegrationFlow newsFlow(){
return IntegrationFlows.from(MessageChannels.queue("newsChannel",10))
.<SyndEntry,String> transform(payload->"《"+payload.getTitle()+"》"+payload.getLink()+getProperty("line.separator"))
.enrichHeaders(Mail.headers() //增加消息头信息
.subject("来自Spring的新闻")
.to("wise-man@126.com")
.from("wisely-man@126.com"))//邮件发送的相关信息
.handle(Mail.outboundAdapter("smtp.126.com")//邮件发送的出站适配器
.port(25)
.protocol("smtp")
.credentials("wisely-man@126.com","****")
.javaMailProperties(p->p.put("mail.debug"),false)),
e->e.id("smtpOut"))
.get();
}
⑥、运行
release.txt文件内容
邮箱接收结果