一、Spring Security简介
1.1 Spring Security的模块
不管你想使用Spring Security保护哪种类型的应用程序,第一件需要做的事就是将Spring Security模块添加到应用程序的类路径下。SpringSecurity 3.2分为11个模块。应用程序的类路径下至少要包含Core和Configuration这两个模块。
模板 | 描述 |
---|---|
ACL | 支持通过访问控制列表(access control list,ACL)为域对象提供安全性 |
切面(Aspects) | 一个很小的模块,当使用Spring Security注解时,会使用基于AspectJ的切面,而不是使用标准的Spring AOP |
CAS客户端(CAS Client) | 提供与Jasig的中心认证服务(Central Authentication Service,CAS)进行集成的功能 |
配置(Configuration) | 包含通过XML和Java配置Spring Security的功能支持 |
核心(Core) | 提供Spring Security基本库 |
加密(Cryptography) | 提供了加密和密码编码的功能 |
LDAP | 支持基于LDAP进行认证 |
OpenID | 支持使用OpenID进行集中式认证 |
Remoting | 提供了对Spring Remoting的支持 |
标签库(TagLibrary) | Spring Security的JSP标签库 |
Web | 提供了Spring Security基于Filter的Web安全性支持 |
1.2 DelegatingFilterProxy
DelegatingFilterProxy是一个特殊的Servlet Filter,它本身所做的工作并不多。只是将工作委托给一个javax.servlet.Filter实现类,这个实现类作为一个<bean>注册在Spring应用的上下文中。可是使用下面其中一种方式进行配置。
1)在web.xml中配置
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/</url-pattern>
</filter-mapping>
2)实现AbstractSecurityWebApplicationInitializer类的子类
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class WebSecurityInitializer extends AbstractSecurityWebApplicationInitializer {
}
1.3 编写简单的安全性配置
1)启用安全功能
@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
2) 配置安全性
通过重载WebSecurityConfigurerAdapter中的一个或多个方法来指定安全性。
方法 | 描述 |
---|---|
configure(WebSecurity) | 通过重载,配置Spring Security的Filter链 |
configure(HttpSecurity) | 通过重载,配置如何通过拦截器保护请求 |
configure(AuthenticationManagerBuilder) | 通过重载,配置user-detail服务 |
import org.springframework.beans.factory.annotation.Configurable;
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;
@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
}
}
二、选择认证用户的方式
Spring Security非常灵活,能够基于各种数据存储来认证用户。它内置了多种常见的用户存储场景,如内存、关系型数据库以及LDAP。但我们也可以编写并插入自定义的用户存储实现。
2.1 使用基于内存的用户存储
因为我们的安全配置类扩展了WebSecurityConfigurerAdapter,因此配置用户存储的最简单方式就是重载configure()方法,并以
AuthenticationManagerBuilder作为传入参数。AuthenticationManagerBuilder有多个方法可以用来配置Spring Security对认证的支持。通过inMemoryAuthentication()方法,我们可以启用、配置并任意填充基于内存的用户存储。
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
}
配置用户信息
除了password()、roles()和and()方法以外,还有其他的几个方法可以用来配置内存用户存储中的用户信息。
方 法 | 描 述 |
---|---|
accountExpired(boolean) | 定义账号是否已经过期 |
accountLocked(boolean) | 定义账号是否已经锁定 |
and() | 用来连接配置 |
authorities(GrantedAuthority…) | 授予某个用户一项或多项权限 |
authorities(List<? extends GrantedAuthority>) | 授予某个用户一项或多项权限 |
authorities(String…) | 授予某个用户一项或多项权限 |
credentialsExpired(boolean) | 定义凭证是否已经过期 |
disabled(boolean) | 定义账号是否已被禁用 |
password(String) | 定义用户的密码 |
roles(String…) | 授予某个用户一项或多项角色 |
2.2 基于数据库表进行认证
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource);// dataSource可以根据实际情况创建
}
}
尽管默认的最少配置能够让一切运转起来,但是它对我们的数据库模式有一些要求。它预期存在某些存储用户数据的表。更具体来说,下面的代码片段来源于Spring Security内部,这块代码展现了当查找用户信息时所执行的SQL查询语句:
在第一个查询中,我们获取了用户的用户名、密码以及是否启用的信息,这些信息会用来进行用户认证。接下来的查询查找了用户所授予的权限,用来进行鉴权,最后一个查询中,查找了用户作为群组的成员所授予的权限。
如果你能够在数据库中定义和填充满足这些查询的表,那么基本上就不需要你再做什么额外的事情了。但是,也有可能你的数据库与上面所述并不一致,那么你就会希望在查询上有更多的控制权。如果是这样的话,我们可以按照如下的方式配置自己的查询。
改变默认的查询语句
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource).usersByUsernameQuery(
"select username, password, true" +
"from TableName where username=?")
.authoritiesByUsernameQuery("select username, 'ROLE_USER' from TableName where username=?");
}
}
使用转码后的密码
如果数据库中的密码进行了转码的话,那么认证就会失败,因为它与用户提交的明文密码并不匹配。为了解决这个问题,我们需要借助passwordEncoder()方法指定一个密码转码器(encoder):
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.StandardPasswordEncoder;
@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource).usersByUsernameQuery(
"select username, password, true" +
"from TableName where username=?")
.authoritiesByUsernameQuery("select username, 'ROLE_USER' from TableName where username=?")
.passwordEncoder(new StandardPasswordEncoder("53cr3t"));
}
}
passwordEncoder()方法可以接受Spring Security中PasswordEncoder接口的任意实现。Spring Security的加密模块包括
了三个这样的实现:BCryptPasswordEncoder、NoOpPasswordEncoder和StandardPasswordEncoder。
上述的代码中使用了StandardPasswordEncoder,但是如果内置的实现无法满足需求时,你可以提供自定义的实现,只需要继承PasswordEncoder
接口在重写自己的方法即可。
2.3 基于LDAP进行认证
为了让Spring Security使用基于LDAP的认证,我们可以使用ldapAuthentication()方法。这个方法在功能上类似于jdbcAuthentication(),只不过是LDAP版本。如下的configure()方法展现了LDAP认证的简单配置:
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.userSearchFilter("(uid={0})")
.groupSearchFilter("member={0}");
}
}
userSearchFilter()和groupSearchFilter()用来为基础LDAP查询提供过滤条件,它们分别用于搜索用户和组。默认情况下,对于用户和组的基础查询都是空的,也就是表明搜索会在LDAP层级结构的根开始。但是我们可以通过指定查询基础来改变这个默认行为。
改变默认行为
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.userSearchBase("ou=people")
.userSearchFilter("(uid={0})")
.groupSearchBase("ou=groups")
.groupSearchFilter("member={0}");
}
}
userSearchBase()属性为查找用户提供了基础查询。同样,groupSearchBase()为查找组指定了基础查询。我们声明用
户应该在名为people的组织单元下搜索而不是从根开始。而组应该在名为groups的组织单元下搜索。
配置密码比对
基于LDAP进行认证的默认策略是进行绑定操作,直接通过LDAP服务器认证用户。另一种可选的方式是进行比对操作。这涉及将输入的密码发送到LDAP目录上,并要求服务器将这个密码和用户的密码进行比对。因为比对是在LDAP服务器内完成的,实际的密码能保持私密。
如果你希望通过密码比对进行认证,可以通过声明passwordCompare()方法来实现。
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.userSearchBase("ou=people")
.userSearchFilter("(uid={0})")
.groupSearchBase("ou=groups")
.groupSearchFilter("member={0}")
.passwordCompare();
}
}
默认情况下,在登录表单中提供的密码将会与用户的LDAP条目中的userPassword属性进行比对。如果密码被保存在不同的属性中,可以通过passwordAttribute()方法来声明密码属性的名称:
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.Md4PasswordEncoder;
@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.userSearchBase("ou=people")
.userSearchFilter("(uid={0})")
.groupSearchBase("ou=groups")
.groupSearchFilter("member={0}")
.passwordCompare()
.passwordEncoder(new Md4PasswordEncoder())
.passwordAttribute("passcode");
}
}
引用远程的LDAP服务器
默认情况下,Spring Security的LDAP认证假设LDAP服务器监听本机的33389端口。但是,如果你的LDAP服务器在另一台机器上,那么可以使用contextSource()方法来配置这个地址。
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.userSearchBase("ou=people")
.userSearchFilter("(uid={0})")
.groupSearchBase("ou=groups")
.groupSearchFilter("member={0}")
.contextSource()
.url("ladp://localhost:389/dc=habuma,dc=com");
}
}
配置嵌入式的LDAP服务器
如果你没有现成的LDAP服务器供认证使用,Spring Security还为我们提供了嵌入式的LDAP服务器。我们不再需要设置远程LDAP服务器的URL,只需通过root()方法指定嵌入式服务器的根前缀就可以了:
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.userSearchBase("ou=people")
.userSearchFilter("(uid={0})")
.groupSearchBase("ou=groups")
.groupSearchFilter("member={0}")
.contextSource()
.root("dc=habuma,dc=com");
}
}
当LDAP服务器启动时,它会尝试在类路径下寻找LDIF文件来加载数据。LDIF(LDAP Data Interchange Format,LDAP数据交换格式)是以文本文件展现LDAP数据的标准方式。每条记录可以有一行或多行,每项包含一个名值对。记录之间通过空行进行分割。
如果你不想让Spring从整个根路径下搜索LDIF文件的话,那么可以通过调用ldif()方法来明确指定加载哪个LDIF文件。
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.userSearchBase("ou=people")
.userSearchFilter("(uid={0})")
.groupSearchBase("ou=groups")
.groupSearchFilter("member={0}")
.contextSource()
.root("dc=habuma,dc=com")
.ldif("classpath:user.ldif");
}
}
2.4 配置自定义的用户服务
假设我们需要认证的用户存储在非关系型数据库中,如Mongo或Neo4j,在这种情况下,我们需要提供一个自定义的
UserDetailsService接口实现。
package org.springframework.security.core.userdetails;
public interface UserDetailsService {
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
我们所需要做的就是实现loadUserByUsername()方法,根据给定的用户名来查找用户。loadUserByUsername()方法会返回代表给定用户的UserDetails对象。如下的程序清单展现了一个UserDetailsService的实现,它会从给定的SpitterRepository实现中查找用户。
实现一个自定义的服务
自定义查找服务的好处是,数据可以来源于任何地方,如关系型数据库、文档数据库或图数据等。
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import java.util.Collection;
public class MyUserServer implements UserDetailsService {
/**
* 根据用户名获取用户信息,权限,角色等
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return null;
}
}
三、拦截请求
对每个请求进行细粒度安全性控制的关键在于重载configure(HttpSecurity)方法。
3.1 定义拦截规则
import org.springframework.beans.factory.annotation.Configurable;
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;
import javax.ws.rs.HttpMethod;
@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/spitters/me").authenticated()
.antMatchers(HttpMethod.POST, "/spittle").authenticated()
.anyRequest().permitAll();
}
}
configure()方法中得到的HttpSecurity对象可以在多个方面配置HTTP的安全性。在这里,我们首先调用authorizeRequests(),然后调用该方法所返回的对象的方法来配置请求级别的安全性细节。其中,第一次调用antMatchers()指定了对“/spitters/me”路径的请求需要进行认证。第二次调用antMatchers()更为具体,说明对“/spittles”路径的HTTP POST请求必须要经过认证。最后对anyRequests()的调用中,说明其他所有的请求都是允许的,不需要认证和任何的权限。antMatchers()方法中设定的路径支持Ant风格的通配符。regexMatchers()方法则能够接受正则表达式来定义请求路径。
authenticated()要求在执行该请求时,必须已经登录了应用。如果用户没有认证的话,Spring Security的Filter将会捕获该请求,并将用户重定向到应用的登录页面。同时,permitAll()方法允许请求没有任何的安全限制。
3.2保护请求的方法
方法 | 能够做什么 |
---|---|
access(String) | 如果给定的SpEL表达式计算结果为true,就允许访问 |
anonymous() | 允许匿名用户访问 |
authenticated() | 允许认证过的用户访问 |
denyAll() | 无条件拒绝所有访问 |
fullyAuthenticated() | 如果用户是完整认证的话(不是通过Remember-me功能认证的),就允许访问 |
hasAnyAuthority(String…) | 如果用户具备给定权限中的某一个的话,就允许访问 |
hasAnyRole(String…) | 如果用户具备给定角色中的某一个的话,就允许访问 |
hasAuthority(String) | 如果用户具备给定权限的话,就允许访问 |
hasIpAddress(String) | 如果请求来自给定IP地址的话,就允许访问 |
hasRole(String) | 如果用户具备给定角色的话,就允许访问 |
not() | 对其他访问方法的结果求反 |
permitAll() | 无条件允许访问 |
rememberMe() | 如果用户是通过Remember-me功能认证的,就允许访问 |
3.3 使用Spring表达式进行安全保护
如下就是使用SpEL表达式来声明具有“ROLE_SPITTER”角色才能访问“/spitter/me”URL:
.antMatchers("/spitters/me").access("hasRole(ROLE_SPITTER)")
Spring Security通过一些安全性相关的表达式扩展了Spring表达式语言
安全表达式 | 计算结果 |
---|---|
authentication | 用户的认证对象 |
denyAll | 结果始终为false |
hasAnyRole(list of roles) | 如果用户被授予了列表中任意的指定角色,结果为true |
hasRole(role) | 如果用户被授予了指定的角色,结果为true |
hasIpAddress(IPAddress) | 如果请求来自指定IP的话,结果为true |
isAnonymous() | 如果当前用户为匿名用户,结果为true |
isAuthenticated() | 如果当前用户进行了认证的话,结果为true |
isFullyAuthenticated() | 如果当前用户进行了完整认证的话(不是通过Remember-me功能进行的认证),结果为true |
isRememberMe() | 如果当前用户是通过Remember-me自动认证的,结果为true |
permitAll | 结果始终为true |
principal | 用户的principal对象 |
例如,如果你想限制“/spitter/me”URL的访问,不仅需要ROLE_SPITTER,还需要来自指定的IP地址,
那么我们可以按照如下的方式调用access()方法:
.antMatchers("/spitters/me").access("hasRole('ROLE_SPITTER') and hasIpAddress('127.0.0.')")
3.4 强制通道的安全性
传递到configure()方法中的HttpSecurity对象,除了具有authorizeRequests()方法以外,还有一个requiresChannel()方法,借助这个方法能够为各种URL模式声明所要求的通道。
import org.springframework.beans.factory.annotation.Configurable;
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;
import javax.ws.rs.HttpMethod;
@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/spitters/me").access("hasRole('ROLE_SPITTER') and hasIpAddress('127.0.0.')")
.antMatchers(HttpMethod.POST, "/spittle").authenticated()
.anyRequest().permitAll()
.and()
.requiresChannel()
.antMatchers("/spitters/form").requiresSecure();// 规定此URL需要使用HTTPS
}
}
不论何时,只要是对“/spitter/form”的请求,Spring Security都视为需要安全通道(通过调用requiresChannel()确定的)并自动将请求重定向到HTTPS上。
与之相反,有些页面并不需要通过HTTPS传送。例如,首页不包含任何敏感信息,因此并不需要通过HTTPS传送。我们可以使
用requiresInsecure()代替requiresSecure()方法,将首页声明为始终通过HTTP传
如果通过HTTPS发送了对“/”的请求,Spring Security将会把请求重定向到不安全的HTTP通道上。
在强制要求通道时,路径的选取方案与authorizeRequests()是相同的。可以使用regexMatchers()方法,通过正则表达式选取路径模式。
四、认证用户
4.1 开启默认登录页
import org.springframework.beans.factory.annotation.Configurable;
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;
import javax.ws.rs.HttpMethod;
@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.
formLogin().and() // 开启默认登录界面,如果没有重写configure方法默认是开启的
.authorizeRequests()
.antMatchers("/home/me").authenticated()
.antMatchers(HttpMethod.POST, "/home").authenticated()
.anyRequest().permitAll();
}
}
2)添加自定义的登录界面
创建登录界面login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Hello World!</h1>
<form action="/authentication/form" method="post" name="f">
用户名:<input type="text" name="username">
用户密码:<input type="password" name="password">
<button type="submit" value="login">login</button>
</form>
</body>
</html>
设置自定义登录界面
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.ComponentScan;
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;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Configurable
@EnableWebSecurity
@EnableWebMvc
@ComponentScan
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/hello","/login.jsp").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
//指定登录页的路径
.loginPage("/hello")
//指定自定义form表单请求的路径
.loginProcessingUrl("/authentication/form")
.failureUrl("/error")
.defaultSuccessUrl("/success")
//必须允许所有用户访问我们的登录页(例如未验证的用户,否则验证流程就会进入死循环)
//这个formLogin().permitAll()方法允许所有用户基于表单登录访问/login这个page。
.permitAll();
//默认都会产生一个hiden标签 里面有安全相关的验证 防止请求伪造 这边我们暂时不需要 可禁用掉
http .csrf().disable();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password(new BCryptPasswordEncoder().encode("123456")).roles("USER").and()
.withUser("admin").password(new BCryptPasswordEncoder().encode("123456")).roles("USER", "ADMIN").and().passwordEncoder(new BCryptPasswordEncoder());
}
}
设置控制器
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HomeController {
@RequestMapping("/hello")
public String hello() {
return "login";
}
@RequestMapping("/error")
public String error() {
return "error";
}
@RequestMapping("/success")
public String success() {
return "success";
}
}
这里值的注意的是表单的用户名name和password输入框的name=""要和security里面的验证的对应:name="username";name="password",否则无法识别,另外action="/authentication/form"要与.loginProcessingUrl("/authentication/form")相对应,原因为: 由于security是由UsernamePasswordAuthenticationFilter这个类定义登录的,里面默认是/login路径,我们要让他用我们的/authentication/form路径,就需要配置.loginProcessingUrl("/authentication/form")
4.2 启用HTTP Basic认证
如果要启用HTTP Basic认证的话,只需在configure()方法所传入的HttpSecurity对象上调用httpBasic()即可。另外,还可以通过调用realmName()方法指定域。
4.3 启用Remember-me功能
对于应用程序来讲,能够对用户进行认证是非常重要的。但是站在用户的角度来讲,如果应用程序不用每次都提示他们登录是更好的。这就是为什么许多站点提供了Remember-me功能,你只要登录过一次,应用就会记住你,当再次回到应用的时候你就不需要登录了。
Spring Security使得为应用添加Remember-me功能变得非常容易。为了启用这项功能,只需在configure()方法所传入的HttpSecurity对象上调用rememberMe()即可。
在这里,我们通过一点特殊的配置就可以启用Remember-me功能。默认情况下,这个功能是通过在cookie中存储一个token完成的,这个token最多两周内有效。但是,在这里,我们指定这个token最多四周内有效(2,419,200秒)。
存储在cookie中的token包含用户名、密码、过期时间和一个私钥——在写入cookie前都进行了MD5哈希。默认情况下,私钥的名为SpringSecured,但在这里我们将其设置为spitterKey,使它专门用于Spittr应用。
如此简单。既然Remember-me功能已经启用,我们需要有一种方式来让用户表明他们希望应用程序能够记住他们。为了实现这一点,登录请求必须包含一个名为remember-me的参数。在登录表单中,增加一个简单复选框就可以完成这件事情:
4.4 退出登录
在应用中,与登录同等重要的功能就是退出。如果你启用Remember-me功能的话,更是如此,否则的话,用户将永远登录在这个系统中。
默认情况下退出功能是通过Servlet容器中的Filter实现的(默认情况下),这个Filter会拦截针对“/logout”的请求。
因此,为应用添加退出功能只需添加如下的链接即可:
<a href="/logout">Logout</a>
当用户点击这个链接的时候,会发起对“/logout”的请求,这个请求会被Spring Security的LogoutFilter所处理。用户会退出应用,所有的Remember-me token都会被清除掉。在退出完成后,用户浏览器将会重定向到“/login?logout”,从而允许用户进行再次登录。
1) 自定义退出后的界面
ogout()提供了配置退出行为的方法。在本例中,调用logoutSuccessUrl()表明在退出成功之后,浏览器需要重定向到“/”。
2)自定义重写默认的LogoutFilter拦截路径
五、保护视图
1.1 使用Spring Security的JSP标签库
1)在jsp中引入
<%@ taglib prefix="securitt" uri="http://www.springframework.org/security/tags" %>
2) 示例
<securitt:authentication property="principal.username" />
3) 标签
JSP标签 | 作用 |
---|---|
security:accesscontrollist | 如果用户通过访问控制列表授予了指定的权限,那么渲染该标签体中的内容 |
security:authentication | 渲染当前用户认证对象的详细信息 |
security:authorize | 如果用户被授予了特定的权限或者SpEL表达式的 计算结果为true,那么渲染该标签体中的内容 |
4)标签属性
认证属性 | 描述 |
---|---|
authorities | 一组用于表示用户所授予权限的GrantedAuthority对象 |
Credentials | 用于核实用户的凭证(通常,这会是用户的密码) |
details | 认证的附加信息(IP地址、证件序列号、会话ID等) |
principal | 用户的基本信息对象 |
示例