笔记43 Spring Security简介
基于Spittr应用
一、Spring Security简介
Spring Security是为基于Spring的应用程序提供声明式安全保护的安全 性框架。Spring Security提供了完整的安全性解决方案,它能够在Web 请求级别和方法调用级别处理身份认证和授权。因为基于Spring框 架,所以Spring Security充分利用了依赖注入(dependency injection, DI)和面向切面的技术。
不管你想使用Spring Security保护哪种类型的应用程序,第一件需要做 的事就是将Spring Security模块添加到应用程序的类路径下,一共有11个模块,应用程序的类路径下至少要包含Core和Configuration这两个模块。
二、过滤Web请求
Spring Security借助一系列Servlet Filter来提供各种安全性功能。DelegatingFilterProxy是一个特殊的Servlet Filter,它本身所做 的工作并不多。只是将工作委托给一个javax.servlet.Filter实现类,这个实现类作为一个<bean>注册在Spring应用的上下文中, 如下图所示:
可以在web.xml进行配置,也可用Java配置。
三、编写简单的安全性配置
1.启用Web安全性功能的最简单配置
SecurityWebInitializer.java
package myspittr.config; import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; @Configuration
@EnableWebSecurity // 启用SpringMVC安全性
public class SecurityWebInitializer extends AbstractSecurityWebApplicationInitializer { }
@EnableWebSecurity注解将会启用Web安全功能。Spring Security必须配置在一个实现了 WebSecurityConfigurer的bean中,或者(简单起见)扩展WebSecurityConfigurerAdapter。在Spring应用上下文中, 任何实现了WebSecurityConfigurer的bean都可以用来配置Spring Security。但如果想指定Web安全的细节,这要通过重载WebSecurityConfigurerAdapter中的一个或多个方法来实现。我们可以通过重载WebSecurityConfigurerAdapter的三 个configure()方法来配置Web安全性,这个过程中会使用传递进来的参数设置行为。
SecuritfConfig.java
package myspittr.config; import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Configuration
@EnableWebSecurity
public class SecuritfConfig extends WebSecurityConfigurerAdapter { @Override
protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic(); }
}
这个简单的默认配置指定了该如何保护HTTP请求,以及客户端认证 用户的方案。通过调用authorizeRequests()和anyRequest().authenticated()就会要求所有进入应用的 HTTP请求都要进行认证。它也配置Spring Security支持基于表单的登录以及HTTP Basic方式的认证。同时,因为我们没有重 载configure(AuthenticationManagerBuilder)方法,所以 没有用户存储支撑认证过程。没有用户存储,实际上就等于没有用户。所以,在这里所有的请求都需要认证,但是没有人能够登录成功。
为了让Spring Security满足应用的需求,还需要再添加一点配置。具体来讲,我们需要:
- 配置用户存储;
- 指定哪些请求需要认证,哪些请求不需要认证,以及所需要的权限;
- 提供一个自定义的登录页面,替代原来简单的默认登录页。
除了Spring Security的这些功能,我们可能还希望基于安全限制,有选择性地在Web视图上显示特定的内容。接下来首先介绍用户认证,即配置用户存储。
四、用户认证
1.使用基于内存的用户存储进行登录认证
因为安全配置类扩展了 WebSecurityConfigurerAdapter,因此配置用户存储的最简单 方式就是重载configure()方法,并以AuthenticationManagerBuilder作为传入参数。AuthenticationManagerBuilder有多个方法可以用来配置 Spring Security对认证的支持。通过inMemoryAuthentication() 方法,可以启用、配置并任意填充基于内存的用户存储。
SecuritfConfig.java
package myspittr.config; import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Configuration
@EnableWebSecurity
public class SecuritfConfig extends WebSecurityConfigurerAdapter { DataConfig dataConfig = new DataConfig(); @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// // TODO Auto-generated method stub
// 启用内存用户存储
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER").and().withUser("admin")
.password("password").roles("USER", "ADMIN"); } @Override
protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic(); }
}
configure()方法中的 AuthenticationManagerBuilder使用构造者风格的接口来构建认证配置。通过简单地调用inMemoryAuthentication()就能启用内存用户存储。调用withUser()方法为内存用户存储添加新的用户,这个方法的参数是username。withUser()方法返回的是UserDetailsManagerConfigurer.UserDetailsBuilder, 这个对象提供了多个进一步配置用户的方法,包括设置用户密码的 password()方法以及为给定用户授予一个或多个角色权限的 roles()方法。
上述程序中,添加了两个用户,“user”和“admin”,密码均为“password”。“user”用户具有USER角色,而“admin”用户具有 ADMIN和USER两个角色。我们可以看到,and()方法能够将多个用户的配置连接起来除了password()、roles()和and()方法以外,还有其他的几个方法可以用来配置内存用户存储中的用户信息。下表描述了 UserDetailsManagerConfigurer.UserDetailsBuilder对象所有可用的方法。
方法 | 描述 |
accountExpired(boolean) | 定义账号是否已经过期 |
accountLocked(boolean) | 定义账号是否已经锁定 |
and() | 用来连接配置 |
authorities(List<? extends GrantedAuthority>) | 授予某个用户一项或多项权限 |
authorities(GrantedAuthority) | 授予某个用户一项或多项权限 |
authorities(String) | 授予某个用户一项或多项权限 |
credentialsExpired(boolean) | 定义凭证是否已经过期 |
disabled(boolean) | 定义账号是否已被禁用 |
password(String) | 定义用户的密码 |
roles(String) | 授予某个用户一项或多项角色 |
当输入用户名和密码后才能进入应用首页。
2.基于数据库表进行认证
用户数据通常会存储在关系型数据库中,并通过JDBC进行访问。为 了配置Spring Security使用以JDBC为支撑的用户存储,我们可以使用jdbcAuthentication()方法,所需的最少配置如下所示:
package myspittr.config; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Configuration
@EnableWebSecurity
public class SecuritfConfig extends WebSecurityConfigurerAdapter { @Autowired
DataSource dataSource; @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// TODO Auto-generated method stub
String query = "select username,password,enabled" + " from slogin where username=?";
String query2 = "select username,authority from slogin where username=?";
auth.jdbcAuthentication().dataSource(dataSource).usersByUsernameQuery(query).authoritiesByUsernameQuery(query2); } @Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic(); }
}
我们必须要配置的只是一个DataSource,这样的话,就能访问关系型数据库了。在这里,DataSource是通过自动装配的技巧得到的。还需要重新设计数据库中的用户表,表名为slogin,具体结构如下所示:
*使用转码后的密码
数据库中的密码通常情况下都进行了转码,所以在用户认证的过程中,即登录过程中需要添加的一个密码转换器。
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
String query = "select username,password,enabled" + " from slogin where username=?";
String query2 = "select username,authority from slogin where username=?";
auth.jdbcAuthentication().dataSource(dataSource).usersByUsernameQuery(query).authoritiesByUsernameQuery(query2)
.passwordEncoder(new StandardPasswordEncoder("li")); }
密码加密:
StandardPasswordEncoder类,是PasswordEncoder接口的(唯一)一个实现类,是本文所述加密方法的核心。它采用SHA-256算法,迭代1024次,使用一个密钥(site-wide secret)以及8位随机盐对原密码进行加密。 随机盐确保相同的密码使用多次时,产生的哈希都不同; 密钥应该与密码区别开来存放,加密时使用一个密钥即可;对hash算法迭代执行1024次增强了安全性,使暴力破解变得更困难些。 盐值不需要用户提供,每次随机生成,加密后得到的密码是80位。
String string = "li";
StandardPasswordEncoder encoder = new StandardPasswordEncoder(string);
System.out.println(encoder.encode("password"));
Spring Security的加密模块包括了三个这样的实现:BCryptPasswordEncoder、NoOpPasswordEncoder和 StandardPasswordEncoder。
密码“password”加密后的结果:
a0620349d440af0a43bf497f501efa4395ea82f9ff4255718a5d58b1bcdf3643615d46c9e9d0b49c
将此结果存入数据库当中,如下图所示:
然后运行项目,可以正确登录!
3.配置自定义的用户服务
假设我们需要认证的用户存储在非关系型数据库中,如Mongo或 Neo4j,在这种情况下,我们需要提供一个自定义的 UserDetailsService接口实现。
UserDetailsService接口非常简单:
我们所需要做的就是实现loadUserByUsername()方法,根据给定 的用户名来查找用户。loadUserByUsername()方法会返回代表给定用户的UserDetails对象。如下的程序清单展现了一 个UserDetailsService的实现,它会从给定的 SpitterRepository实现中查找用户。
UserDetails.java
package myspittr.config; import java.util.ArrayList;
import java.util.List; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import myspittr.data.SpitterRepositorys;
import myspittr.spitter.Spitter; public class UserDetails implements UserDetailsService {
private final SpitterRepositorys spitterRepositorys; public UserDetails(SpitterRepositorys spitterRepositorys) {
this.spitterRepositorys = spitterRepositorys;
} public org.springframework.security.core.userdetails.UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
// TODO Auto-generated method stub
Spitter spitter = spitterRepositorys.findByUsername(username);
if (spitter != null) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("USER"));
return new User(spitter.getUsername(), spitter.getPassword(), authorities);
}
throw new UsernameNotFoundException("User '" + username + "' not found");
} }
UserDetails并不知道用户数据存储在什么地方。设置进来的SpitterRepository能够从关系型数据 库、文档数据库或图数据中查找Spitter对象,甚至可以伪造一 个。SpitterUserService不知道也不会关心底层所使用的数据存 储。它只是获得Spitter对象,并使用它来创建User对象。(User 是UserDetails的具体实现。) 为了使用SpitterUserService来认证用户,可以通过 userDetailsService()方法将其设置到安全配置中:
@Autowired
SpitterRepositorys spitterRepositorys;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(new UserDetails(spitterRepositorys));
}
userDetailsService()方法(类似于 jdbcAuthentication()、ldapAuthentication以及 inMemoryAuthentication())会配置一个用户存储。不过,这 里所使用的不是Spring所提供的用户存储,而是使用UserDetailsService的实现。
使用原来spitter表中注册的用户进行登录,用户名:w123123 密码:123123
spitter表: slogin表:
结果:
五、拦截请求
1.web应用路径保护
在任何应用中,并不是所有的请求都需要同等程度地保护。有些请求 需要认证,而另一些可能并不需要。有些请求可能只有具备特定权限的用户才能访问,没有这些权限的用户会无法访问。
例如,考虑Spittr应用的请求。首页当然是公开的,不需要进行保护。类似地,因为所有的Spittle都是公开的,所以展现Spittle 的页面不需要安全性。但是,创建Spittle的请求只有认证用户才能执行。同样,如果处理“/spitters/me”请求,并展现当前用户的基本信息时, 那么就需要进行认证,从而确定要展现谁的信息。
对每个请求进行细粒度安全性控制的关键在于重载configure(HttpSecurity)方法。如下的代码片段展现了重载的configure(HttpSecurity)方法,它为不同的URL路径有选择地应用安全性:
@Override
protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/spitter/me/**").authenticated().antMatchers(HttpMethod.POST, "/spittles")
.authenticated().anyRequest().permitAll().and().formLogin().and().httpBasic();
}
configure()方法中得到的HttpSecurity对象可以在多个方面配 置HTTP的安全性。在这里,我们首先调用authorizeRequests(),然后调用该方法所返回的对象的方法来配置请求级别的安全性细节。其中,第一次调用antMatchers() 指定了对“/spitters/me/**”路径的请求需要进行认证。第二次调用antMatchers()更为具体,说明对“/spittles”路径的HTTP POST请求必须要经过认证。最后对anyRequests()的调用中,说明其他所有的请求都是允许的,不需要认证和任何的权限。
antMatchers()方法中设定的路径支持Ant风格的通配符。
未启用路径保护前可以对http://localhost:8080/com.li.Spittr/spitter/me/lyj123123直接进行访问,并显示用户个人信息,但是当启用了路径保护后,再次访问时就会跳转到默认的登录页面,如下所示:
我们所配置的安全性能够不仅仅限于认证 用户。例如,我们可以修改之前的configure()方法,要求用户不 仅需要认证,还要具备USER权限:
@Override
protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/spitter/me/**").hasAuthority("USER")
.antMatchers(HttpMethod.POST, "/spittles").authenticated().anyRequest().permitAll().and().formLogin()
.and().httpBasic();
}
修改UserDetails中的loadUserByUsername方法,当用户名是lyj123123为其添加用户权限USER,其余添加USER_N,具体代码如下所示:
public org.springframework.security.core.userdetails.UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
// TODO Auto-generated method stub
Spitter spitter = spitterRepositorys.findByUsername(username);
if (spitter != null) { List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
if (spitter.getUsername().equals("lyj123123")) {
authorities.add(new SimpleGrantedAuthority("USER"));
} else {
authorities.add(new SimpleGrantedAuthority("USER_N"));
}
return new User(spitter.getUsername(), spitter.getPassword(), authorities);
}
throw new UsernameNotFoundException("User '" + username + "' not found");
}
所以当使用其他用户名进行登录时会报错因为缺少权限(用户名:zzc123123 密码:123123),如下所示:
我们可以将任意数量的antMatchers()、regexMatchers()和 anyRequest()连接起来,以满足Web应用安全规则的需要。但是, 我们需要知道,这些规则会按照给定的顺序发挥作用。所以,很重要 的一点就是将最为具体的请求路径放在前面,而最不具体的路径(如 anyRequest())放在最后面。如果不这样做的话,那不具体的路 径配置将会覆盖掉更为具体的路径配置。
2.强制通道的安全性
使用HTTP提交数据是一件具有风险的事情。如果使用HTTP发送无关 紧要的信息,这可能不是什么大问题。但是如果你通过HTTP发送诸 如密码和信用卡号这样的敏感信息的话,那你就是在找麻烦了。通过 HTTP发送的数据没有经过加密,黑客就有机会拦截请求并且能够看 到他们想看的数据。这就是为什么敏感信息要通过HTTPS来加密发送 的原因。通过在URL中的HTTP后添加“s”我们就能很容易地实现页面的安全性,但是忘记添加“s”同样也是很容易出现的。
作为示例,可以参考Spittr应用的注册表单。尽管Spittr应用不需要信用卡号、社会保障号或其他特别敏感的信息,但用户有可能仍然希望信息是私密的。为了保证注册表单的数据通过HTTPS传送,我们可以在配置中添加requiresChannel()方法,如下所示:
@Override
protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/spitter/me/**").hasAuthority("USER")
.antMatchers(HttpMethod.POST, "/spittles").authenticated().anyRequest().permitAll().and()
.requiresChannel().antMatchers("/spitter/register").requiresSecure().and().formLogin().and()
.httpBasic();
}
启用前后的对比
如何启用tomcat的https协议请参考:tomcat启用https协议
3.防止跨站请求伪造
我们可以回忆一下,当一个POST请求提交到“/spittles”上 时,SpittleController将会为用户创建一个新的Spittle对象。但是,如果这个POST请求来源于其他站点的话,会怎么样呢?如果在其他站点提交如下表单,这个POST请求会造成什么样的结果呢?
<sf:form method="POST" action="http://localhost:8080/com.li.Spittr/spittles" enctype="multipart/form-data" modelAttribute="pubSpittle">
<sf:input type="hidden" path="title" value="I'm stupid !"/>
<sf:input type="hidden" path="message" value="I'm stupid !"/>
<sf:input type="hidden" path="username" value="zzc123123"/>
<sf:input type="hidden" path="spittlePictureString" value="zzc123123"/>
<input type="submit" value="发布" />
</sf:form>
假设你禁不住获得和美女聊天的诱惑,点击了按钮——那么你将会提交表单到如下地址http://localhost:8080/com.li.Spittr/spittles。如果你已经登录到了 spittr,那么这就会广播一条消息,让每个人都知道你做了一件蠢事。这是跨站请求伪造(cross-site request forgery,CSRF)的一个简单样例。从Spring Security 3.2开始,默认就会启用CSRF防护。Spring Security通过一个同步token的方式来实现CSRF防护的功能。它将会拦截状态变化的请求(例如,非GET、HEAD、OPTIONS和 TRACE的请求)并检查CSRF token。如果请求中不包含CSRF token的话,或者token不能与服务器端的token相匹配,请求将会失败,并抛出CsrfException异常。
这意味着在你的应用中,所有的表单必须在一个“_csrf”域中提交 token,而且这个token必须要与服务器端计算并存储的token一致,这 样的话当表单提交的时候,才能进行匹配。Spring Security已经简化了将token放到请求的属性中这一 任务。如果你使用Thymeleaf作为页面模板的话,只要<form>标签的 action属性添加了Thymeleaf命名空间前缀,那么就会自动生成一 个“_csrf”隐藏域:
如果使用JSP作为页面模板的话,如下代码所示:
更好的功能是,如果使用Spring的表单绑定标签的话,<sf:form>标 签会自动为我们添加隐藏的CSRF token标签。
处理CSRF的另外一种方式就是根本不去处理它。我们可以在配置中 通过调用csrf().disable()禁用Spring Security的CSRF防护功能, 如下所示:
演示:
因为默认CSRF是启用的,所以跨站访问的时候会报错。
当关闭CSRF防护后,再次点击发布时就会成功发送消息,如下图:
六、视图保护
笔记43 Spring Security简介的更多相关文章
- Spring Security 简介
本文引自:https://blog.csdn.net/xlecho/article/details/80026527 在 Web 应用开发中,安全一直是非常重要的一个方面.安全虽然属于应用的非功能性需 ...
- 转 Spring Security 简介
https://blog.csdn.net/xlecho/article/details/80026527 Spring Security 简介 2018年04月21日 09:53:02 阅读数:13 ...
- Spring Security简介与入门Demo
1:Spring Security简介 Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在Spring应用上下文中配 ...
- Spring Security学习笔记-自定义Spring Security过滤链
Spring Security使用一系列过滤器处理用户请求,下面是spring-security.xml配置文件. <?xml version="1.0" encoding= ...
- spring security简介与使用
目录 spring security 新建一个springboot项目 添加spring security 登录 使用默认用户和随机生成的密码登录 使用yaml文件定义的用户名.密码登录 使用代码中指 ...
- spring Security简介
它是spring的权限管理框架
- 笔记43 Spring Web Flow——订购披萨应用详解
一.项目的目录结构 二.订购流程总体设计 三.订购流程的详细设计 1.定义基本流程pizza-flow.xml <?xml version="1.0" encoding=&q ...
- 吴裕雄--天生自然JAVA SPRING框架开发学习笔记:Spring JDBCTemplate简介
Spring 框架针对数据库开发中的应用提供了 JDBCTemplate 类,该类是 Spring 对 JDBC 支持的核心,它提供了所有对数据库操作功能的支持. Spring 框架提供的JDBC支持 ...
- spring security 简介+实战
过滤器链: 依赖: security 功能列表: 一.登录验证.权限验证 1.1 httpbasic验证 1.2form验证 建立数据需要遵循RBAC模型 用户表要参考UserDetail创建 实例类 ...
随机推荐
- Android Studio javadoc 生成注释文档
相信大家刚开始写代码的时候就被前辈告知了要养成写注释的好习惯,今天我们来了解一下如何利用我们平时写的注释生成文档,一起来看看吧! 其实注释格式一般如下两种: /* *普通多行 *注释 */ / ...
- tf.placeholde函数解释与用法
函数原型:tf.placeholder(dtype, shape=None, name=None) 使用说明:该函数用于得到传递进来的真实的训练样本.同时也可以理解为形参, 用于定义过程,在执行的时候 ...
- pair queue____多源图广搜
.简介 class pair ,中文译为对组,可以将两个值视为一个单元.对于map和multimap,就是用pairs来管理value/key的成对元素.任何函数需要回传两个值,也需要pair. 该函 ...
- Spring下载maven
http://maven.springframework.org/release/org/springframework/spring/
- OpenCV的安装与配置
1.去官网下载opencv,在本教程中选用的时opencv3.4.1,其他版本的配置方法异曲同工.下载链接http://opencv.org/releases.html,选择sources版本 2.解 ...
- oracle查询重复数据出现次数
话不多数上代码: 我在Oracle数据库查数据,发现重复数据,于是我想把重复条数以及具体数据查出来: 下面是数据 然后我需要知道重复多少条 (重复十条,也就是有五条数据相同) SQL: select ...
- elasticsearch依赖的jackson-jar包与jboss依赖的jackson-jar包“版本”冲突
elasticsearch依赖的jackson-jar包与jboss依赖的jackson-jar包“版本”冲突,导致elasticsearch相关功能在本地tomcat服务器正常,但是部署到jboss ...
- Clickhouse集群部署
1.集群节点信息 10.12.110.201 ch201 10.12.110.202 ch202 10.12.110.203 ch203 2. 搭建一个zookeeper集群 在这三个节点搭建一个zo ...
- 关于nodejs+koa中的跨域问题与koa项目创建
项目快速创建 -1. 安装koa-generator npm install -g koa-generator -2. 使用koa-generator生成koa2项目, koa2 test -3. 完 ...
- es批量索引
使用Python操作Elasticsearch数据索引的教程 这篇文章主要介绍了使用Python操作Elasticsearch数据索引的教程,Elasticsearch处理数据索引非常高效,要的朋友可 ...