什么是Spring Security
来看看官网给出的概念
百度翻译一下:springsecurity是一个框架,提供身份验证、授权和针对常见攻击的保护。由于对命令式应用程序和反应式应用程序的一流支持,它是保护基于Spring的应用程序的事实上的标准。
注意两个关键词
- 身份验证(authentication)
- 授权(authorization)
这就是这个框架主要帮我们干的活了,所谓身份验证就是看你登录的用户是否合法,数据库有没有记录;授权就是当前访问的用户有什么权限,比如普通用户,管理员,什么样的权限对应了能访问什么权限的资源。
Spring Security基本原理
Spring Security本质是一个过滤链,我们从Client发出请求,然后经过一系列的Filter最终到Servlert执行,官方给出的图片如下
刚开始会调用DelegatingFilterProxy,这个filter的doFilter方法如下
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// Lazily initialize the delegate if necessary.
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
synchronized (this.delegateMonitor) {
delegateToUse = this.delegate;
if (delegateToUse == null) {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: " +
"no ContextLoaderListener or DispatcherServlet registered?");
}
delegateToUse = initDelegate(wac);//点开这个init方法
}
this.delegate = delegateToUse;
}
}
// Let the delegate perform the actual doFilter operation.
invokeDelegate(delegateToUse, request, response, filterChain);
}
initDelegate(wac)方法如下
接下来看看FilterChainProxy这个过滤器干了啥
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
if (!clearContext) {
doFilterInternal(request, response, chain);//点开这个方法
return;
}
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
doFilterInternal(request, response, chain);
}
catch (RequestRejectedException ex) {
this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response, ex);
}
finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
总结就是先是调用DelegatingFilterProxy得到FilterChainProxy,然后FilterChainProxy创建个集合得到所有的filter,然后就是invokeDelegate()这个方法开始执行我们的过滤链了。
Spring Security的使用
一般的组合方式为
- ssm+shiro
- springboot/springcloud+spring security
这里我使用的是springboot,首先我们需要先导入依赖,在pom.xml添加
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
身份认证
首先我们需要创建几个简单的页面,再写个controller,这里我就不罗列出来了,现在我们只是导入了依赖,还没有自定义进行任何配置,启动项目访问试试,我们会发现在项目启动时候控制台多了串字符,这就是我们待会需要密码
我们输入我们写好的访问路径,会发现我们并没有进入我们要访问的页面,而是进入了一个登入界面,其实这时候Spring Security已经开始工作了,它拦截了我们的请求,发现我们并没有进行身份认证,就调到了登录页,这个登录页默认的访问路径是"/login",是这个框架自带的,然后输入我们在控制台获得的密码就能进入我们刚才想访问的页面。但是实际应用中我们需要自己控制能登陆的用户有哪些,这就需要自定义配置了。
自定义用户
一、配置文件配置
在你的yml或者properties文件中配置
二、配置类中配置
创建一个类继承WebSecurityConfigurerAdapter,并使用@EnableWebSecurity注解
重写其中的下面这个方法
代码如下
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder passwordEncoder=new BCryptPasswordEncoder();
auth.inMemoryAuthentication()//这是在内存中保存我们创建的用户
.passwordEncoder(passwordEncoder)//设置我们的加密方式
.withUser("mike")//用户名
.password(passwordEncoder.encode("123456"))//密码
.roles("vip1");//拥有的权限
}
ps:我们在这里设置的权限提交后后台会默认帮我们加上"ROLE_",也就是我们设置的是vip1,实际上保存的是ROLE_vip1
三、实现UserDetailsService接口,重写里面的方法
我们最后的实现肯定是要在数据库中获取用户信息的这时候就可以用第三种方式,代码如下
@Service
public class MydetailService implements UserDetailsService {
@Autowired
private Usermapper usermapper;//这里我用的是mybatis操作数据库,并且已经连接了,注入查询需要用到的mapper对象
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
Users users=usermapper.getone(s);//获取从客户端得到的表单里面的用户名并从数据库中查找,有就赋值给我们自定义的pojo类Users
if(users==null){
throw new UsernameNotFoundException("没有找到该用户");
}
PasswordEncoder passwordEncoder=new BCryptPasswordEncoder();
String password= passwordEncoder.encode(users.getPassword());
List<GrantedAuthority> authorities= AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_admins,ROLE_vip1");//设置用户权限,这里记得加上ROLE_
User user=new User(users.getName(),password,authorities);//当前方法需要返回一个UserDetails对象,User就实现了这个接口,创建我们的用户并且返回
return user;
}
}
再在我们的配置类中的configure(AuthenticationManagerBuilder auth)方法中显式使用一下,别忘了在当前配置类中@Autowird注入一下你的MydetailService对象
我们还需要在配置类中注册一个Bean
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
授权,自定义登录页面
在我们创建用户的时候已经给他们分配了相应的权限,如何根据这些权限来控制访问登录呢,我们需要重写下面这个方法
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()//开启请求授权
.antMatchers("/tologin").permitAll()//允许不需要权限访问/tologin
.antMatchers("/vip/1").hasRole("vip1")//需要ROLE_vip1权限才能访问当前路径,以下类似
.antMatchers("/vip/2").hasRole("vip2")
.antMatchers("/vip/3").hasRole("vip3")
.anyRequest().authenticated();//其他的所有请求都会被拦截进行认证
http.formLogin().loginPage("/tologin")//设置默认的访问页面,这里写的是controller里的访问路径
.defaultSuccessUrl("/index")//访问成功后跳到的页面
.usernameParameter("username")//设置提交表单规定的name
.passwordParameter("password")//设置提交表单规定的name
.loginProcessingUrl("/login");//登入处理路径,可以不用实现,表单提交的action也用这个
http.csrf().disable();//关闭跨域请求,有兴趣的可以了解下csrf攻击
}
这里需要注意的是表单提交的方式一定是post,原因看看下面UsernamePasswordAuthenticationFilter(这个类就是验证我们表单信息的)这个类的源码
默认表单input的名字分别是这两个,也可以自定义设置,上面给出了方法
-------测试一下-------1.输入访问路径,首先是跳到了我们的登录页面,因为我们还没有进行身份认证
2.输入我在数据库中注册好的用户,该用户拥有vip1的权限,通过认证进入了我们的主页
3.点击相对应的vip1,vip2,vip3标签点击vip1发现可以正常访问,因为我们有权限
点击vip2,vip3,发现报错403,因为我们没有权限,这里我重写了403页面
注解实现授权
注意:如果要使用
@PostAuthorize@PretAuthorize
@PostFilter@PreFilter
需要在启动类上加上
登出
我这里使用的是a标签来实现登出,只需要让a标签的url为 /logout
然后在配置文件的 protected void configure(HttpSecurity http)方法加上如下代码
http.logout()//开启登出
.logoutUrl("/logout")//登出时候的处理,可以不用实现
.logoutSuccessUrl("/tologin");//登出成功时候的跳转
然后我们的登出就实现了,点击退出登录,我们就退到了登录页面,同时也清除了相对应的Session
记住我
我们平常在登录某个网站的时候,登录时基本都有个记住我的选项,勾上下次就不用再输入用户名和密码再次登录了,以下是实现方式,在登入界面设置个checkbox
![在这里插入图片描述](
name一定要为remember-me,然后在配置文件的 protected void configure(HttpSecurity http)方法加上如下代码
http.rememberMe().tokenValiditySeconds(6000)//设置有效时间,单位是秒
.userDetailsService(mydetailService);
在配置类中加入以下代码
@Autowired
private DataSource dataSource;
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository=new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
演示效果如下
登入成功后查询cookies,发现多了个remember-me
关闭浏览器,重新打开浏览器访问主页,发现直接进去,不需要认证,成功实现。其中的原理就是我们在登入的时候创建了一个cookies,一个存在浏览器,一个存在我们数据库的表里,当我们再次登入时,带着cookie去数据库找,找到了就自动登入了,数据库的表是我们刚注册的Bean PersistentTokenRepository帮我们创建的,源码如下:
查找数据库发现也存在这个表
总结
Spring Security的常用使用我就想到这么多了,但我觉得学习框架之前还是要把原理好好学一下,可以先试试用原生的代码如何实现,就不至于总是面向框架编程。