在web应用开发中,安全无疑是十分重要的。在之前使用filter来进行过滤,实现安全,但是要写大量的代码,而且实现的效果还很一般。spring提供Spring Security来保护web应用,是一个非常好的选择。Spring Security 是spring项目之中的一个安全模块,可以非常方便与spring项目无缝集成。特别是在spring boot项目中加入spring security更是十分简单。
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.thymeleafgroupId>
<artifactId>thymeleaf-spring5artifactId>
dependency>
<dependency>
<groupId>org.thymeleaf.extrasgroupId>
<artifactId>thymeleaf-extras-java8timeartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
dependencies>
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class RouterController {
//设置访问主页
@RequestMapping({"/","/index"})
public String index(){
return "index";
}
//设置访问登陆页面
@RequestMapping("/toLogin")
public String toLogin(){
return "views/login";
}
//设置访问level1下的页面
@RequestMapping("/level1/{id}")
public String level1(@PathVariable("id")int id){
return "views/level1/"+id;
}
//设置访问level2下的页面
@RequestMapping("/level2/{id}")
public String level2(@PathVariable("id")int id){
return "views/level2/"+id;
}
//设置访问level3下的页面
@RequestMapping("/level3/{id}")
public String level3(@PathVariable("id")int id){
return "views/level3/"+id;
}
}
@EnableWebSecurity
public class SecurityConfig 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")
http.formLogin().loginPage("/toLogin").usernameParameter("user").passwordParameter("pwd").loginProcessingUrl("/login");
http.csrf().disable();
//http.logout().deleteCookies("remove").invalidateHttpSession(true);
http.logout().logoutSuccessUrl("/");
http.rememberMe().rememberMeParameter("remeber");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("惠杰超可爱").password(new BCryptPasswordEncoder().encode("123")).roles("vip2","vip3")
.and()
.withUser("阿猛").password(new BCryptPasswordEncoder().encode("123")).roles("vip1");
}
}
自动开启Springsecurity功能,@EnableWebSecurity
注解有两个作用,1: 加载了WebSecurityConfiguration配置类, 配置安全认证策略。2: 加载了AuthenticationConfiguration, 配置了认证信息。
来看看他是怎样实现的。
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,
SpringWebMvcImportSelector.class,
OAuth2ImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
/**
* Controls debugging support for Spring Security. Default is false.
* @return if true, enables debug support with Spring Security
*/
boolean debug() default false;
}
@EnableWebSecurity
引入了@EnableGlobalAuthentication
注解,
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import(AuthenticationConfiguration.class)
@Configuration
public @interface EnableGlobalAuthentication {
}
在这个注解类中,又@Import(AuthenticationConfiguration.class)
引入了AuthenticationConfiguration配置类, 这个类是来配置认证相关的核心类, 这个类的主要作用是,向spring容器中注入AuthenticationManagerBuilder,
其实是使用了建造者模式, 他能建造AuthenticationManager,这是身份认证的入口。
@Import({ WebSecurityConfiguration.class,SpringWebMvcImportSelector.class,OAuth2ImportSelector.class })
:激活引入了WebSecurityConfiguration
配置类。进入这个类我们可以看见有很多@Bean和@Autowrite,以为和这个这个配置类,准备了很多bean对象在容器中。下面是一个很重要的SpringSecurityFilterChain
这是Spring Secuity的核心过滤器, 这是请求的认证入口,字面意思就可以知道他是spring的安全过滤器
//AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME定义在这个类中
public abstract class AbstractSecurityWebApplicationInitializer
implements WebApplicationInitializer {
private static final String SERVLET_CONTEXT_PREFIX = "org.springframework.web.servlet.FrameworkServlet.CONTEXT.";
public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain";
private final Class<?>[] configurationClasses;
}
@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
/**
* Creates the Spring Security Filter Chain
* @return the {@link Filter} that represents the security filter chain
* @throws Exception
*/
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
//AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME定义在上面类中
public Filter springSecurityFilterChain() throws Exception {
boolean hasConfigurers = webSecurityConfigurers != null
&& !webSecurityConfigurers.isEmpty();
if (!hasConfigurers) {
WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
.postProcess(new WebSecurityConfigurerAdapter() {
});
webSecurity.apply(adapter);
}
return webSecurity.build();
}
}
@Order(100)
public abstract class WebSecurityConfigurerAdapter implements
WebSecurityConfigurer<WebSecurity> {
}
他实现了WebSecurityConfigurer接口,WebSecurityConfigurer接口,
这个类是 网络安全配置器适配器,适配器类的作用就是,他实现接口的方法,我们要使用接口时,不需要去直接实现接口(那就必须得实现里面的所有方法),而是通过继承适配器类(用哪个方法,重写那个方法即可)的方式。
例如:我们在配置的时候,需要我们自己写个配置类去继承他,然后编写自己所特殊需要的配置即可。
这就是我们可以重写的方法。
springsecurity采用链式编程风格,所以你看见的配置全是链式的。
我们通过分析WebSecurityConfigurerAdapter 类中我们用到的这两个方法的源码来分析。
/**
* Override this method to configure the {@link HttpSecurity}. Typically subclasses
* should not invoke this method by calling super as it may override their
* configuration. The default configuration is:
*
*
* http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
*
*
* @param http the {@link HttpSecurity} to modify
* @throws Exception if an error occurs
*/
// @formatter:off
protected void configure(HttpSecurity http) throws Exception {
logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic();
}
}
那段注释中文翻译为:重写此方法以配置{@linkHttpSecurity}。 典型的子类不应调用super来调用此方法,因为它可能覆盖它们的配置。 默认配置是:http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//授权
@Override
protected void configure(HttpSecurity http) throws Exception {
//请求授权的规则
http.authorizeRequests()//拦截url注册
.antMatchers("/").permitAll()//匹配“/”,即主页所有人可以访问
.antMatchers("/level1/**").hasRole("vip1")//配置“/level1”开头的所有请求,只有vip1权限的人才可以访问
.antMatchers("/level2/**").hasRole("vip2")//配置“/level2”开头的所有请求,只有vip2权限的人才可以访问
.antMatchers("/level3/**").hasRole("vip3");//配置“/level3”开头的所有请求,只有vip3权限的人才可以访问
//没有权限时跳转登陆页面
//http.formLogin();//默认登录页面,跳转到springsecurity自己定义的login页面去,1.0的版本比较简陋,到2.0时这个默认登陆界面已经很漂亮了
http.formLogin().loginPage("/toLogin").usernameParameter("user").passwordParameter("pwd").loginProcessingUrl("/login");//跳转到自定义登陆页
/*
这里要注意,springsecurity自定义的login页面中,用户名和密码的name属性值是username和password,这里的usernameParameter(String str)、passwordParameter(String str)方法就是当我们自定义的登陆页面中这两个输入框的name属性值不一致设计的。像我们用户名和密码的name属性值分别是user和pwd。
*/
//注销,并跳到主页
http.csrf().disable();//关闭csrf功能,这是登陆失败可能的原因
//http.logout().deleteCookies("remove").invalidateHttpSession(true);//也可以实现
http.logout().logoutSuccessUrl("/");//点击注销功能,请求“/”,即主页面
//开启记住我功能
http.rememberMe().
rememberMeParameter("remeber"); //用于指示在登录时记住用户的HTTP参数。
}
}
认证方法。
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//认证
//密码编码:PasswordEncoder类
//在springsecurity5.0+新增了很多加密方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//这里是从内存中读取的固定用户,一般是从数据库读取的,但是密码需要加密
//对密码加密,BCryptPasswordEncoder只是密码加密的一种
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("惠杰超可爱").password(new BCryptPasswordEncoder().encode("123")).roles("vip2","vip3")
.and()
.withUser("阿猛").password(new BCryptPasswordEncoder().encode("123")).roles("vip1");
}
}
类似于spring security的xml配置文件命名空间配置中的元素。它允许对特定的http请求基于安全考虑进行配置。
默认情况下,适用于所有的请求,但可以使用requestMatcher(RequestMatcher)或者其它相似的方法进行限制。