Spring Security
一、简介
Spring Security从两个角度解决安全问题:
1)使用Servlet规范中的Filter保护Web请求并限制URL级别的访问。
2)使用Spring AOP保护方法调用-----借助于于对象代理和使用通知,
能够确保只有具备适当权限的用户才能访问安全保护的方法。
1.过滤Web请求
Spring Security借助一系列的Servlet Fliter来过滤web请求。我们只需要配置一个Filter。
DelegatingFilterProxy是一个特殊的Servlet Filter,它将工作委托给一个javax.servlet.Filter实现类,
这个实现类作为一个<bean>注册在Spring应用上下文中。
有一个名为springSecurityFilterChain的Filter bean, DelegatingFilterProxy会将过滤逻辑委托给它。
以Java方式配置DelegatingFilterProxy:
public class SecurityWebInitializer extends AbstractSecurityWebApplicationInitializer{ }
配置好DelegatingFilterProxy后,它会拦截发往应用中的请求,并将请求委托给ID为springSecurityFilterChain bean。
2.编写简单的安全性配置
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter{ }
Spring Security必须配置在实现了WebSecurityConfigurger的bean中,或者
扩展WebSecurityConfigurerAdapter。
通过重载WebSecurityConfigurerAdapter中方法,可以指定web安全的细节。
二、用户存储
用户存储:也就是用户名、密码以及其他信息存储的地方,在进行决策的时候,会对其进行检索。
1.基于内存的用户存储
方式:重载WebSecurityConfigureAdapter中的configure方法。
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception{ //启用内存用户存储 //and链接多个用户配置 auth.inMemoryAuthentication().withUser("user") .password("password").roles("USER").and() .withUser("tang").password("password").roles("USER", "ADMIN"); }
3.基于数据库表进行认证
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter{ @Autowired DataSource dataSource; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception{ auth.jdbcAuthentication().dataSource(dataSource); } }
重写默认的用户查询功能:
Spring Security内部默认的查询sql:
配置自己的查询:
@Autowired DataSource dataSource; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception{ auth.jdbcAuthentication().dataSource(dataSource) .usersByUsernameQuery("select username, password, flag " + "from user_info where username=?").authoritiesByUsernameQuery( "select username, 'ROLE_USER' from user_info where username=?" ); }
使用转码后的密码:
上面的认证查询会预期密码存储在数据库中,如果数据库中的密码进行了转码的话,
那么认证就会失败,因为它与用户提交的明文密码并不匹配。为了解决这个问题,我们
需要借助passwordEncoder()方法指定一个密码转码器。
public interface PasswordEncoder { String encode(CharSequence var1); boolean matches(CharSequence var1, String var2); default boolean upgradeEncoding(String encodedPassword) { return false; } }
protected void configure(AuthenticationManagerBuilder auth) throws Exception{ auth.jdbcAuthentication().dataSource(dataSource) .usersByUsernameQuery("select username, password, flag " + "from user_info where username=?").authoritiesByUsernameQuery( "select username, 'ROLE_USER' from user_info where username=?" ).passwordEncoder(new StandardPasswordEncoder("xsf")); }
4.配置自定义的用户服务
假设需要认证的用户存储在非关系型数据库中,如Mongo或Neo4j,这种情况下
我们需要提供一个自定义的UserDetailService接口实现。
public interface UserDetailsService { UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; }
要做的就是实现loadUserByUsername()方法,根据给定的用户名查找用户。该接口不关心,也不知道底层所使用的用户存储。
为了使用UserDetailService来认证用户,我们可以通过AuthenticationManagerBuilder的userDetailsService()方法设置它。
三、拦截请求
方法:通过重载configure(HttpSecurity),为不同的URL路径有选择地应用安全性。
@Configuration @EnableWebSecurity //启用Spring Security的Web安全支持,并提供Spring MVC集成 public class WebSecurityConfig extends WebSecurityConfigurerAdapter { //定义了哪些URL路径应该被保护,哪些不应该 @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/", "/home").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); } }
authenticated()要求在执行该请求时,必须已经登陆了应用。如果用户没有认证的话,Spring Security的Filter将会捕获该请求,
并将用户重定向到应用的登陆页面。同时,permitAll()方法允许请求没有任何安全限制。
1.使用Spring表达式进行安全保护
借助access()方法,可以将SpEL作为声明访问限制的一种方式。例如:
http.authorizeRequests() .antMatchers("/", "/home") .access("hasRole('ROLE_USER') and hasIpAddress(192.168.1.2)");
2.强制通道的安全性
传递到configure()方法中的HttpSecurity对象,有一个requiresChannel()方法,
借助这个方法能够为各种URL模式声明所要求的通道。
http.requiresChannel().antMatchers("/home").requiresSecure(); //需要https
四、认证用户
1.启用HTTP Basic认证
HTTP Basic认证会直接通过HTTP请求本身,对要访问的应用程序的用户进行认证。
本质上,它是一个HTTP401响应,表面必须要在请求中包含一个用户名和密码。
方法:在HttpSecurity对象上调用httpBasic()即可。
2.启用Remember-me功能
方法:在HttpSecurity对象上调用remeberMe()即可。
@Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .loginPage("/login") .and() .rememberMe() .tokenValiditySeconds(2419200) .key("tangKey");
存储在cookie中的token包含用户名、密码、过期时间和一个私钥----在写入cookie前都
进行了MD5哈希。默认情况下,私钥名为SpringSecured,这里我们更改了它。
用户需要操作:
<input id="remeber_me" name="remeber_me" type="checkbox"/>
3.退出
退出功能是通过Servlet容器中的Filter实现的,这个Filter会拦截针对"/logout"的请求。添加退出功能:
<a th:href="@{/logout}">Logout</a>
如果希望用户被重定向到其他页面:
@Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .loginPage("/login") .and() .logout() .logoutSuccessUrl("/"); //退出成功后重定向到/ }
五、保护视图
1.使用Spring Security的JSP标签
首先在JSP页面中声明该标签库:
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
Hello <security:authentication property="principal.username">
条件性的渲染内容:
<sec:authorize access="hasRole('ROLE_TANG')"
因为已经在Spring Security中声明了安全性约束:
<security:authorize url="/admin"> <spring:url value="/admin" var="admin_url" /> <br/><a href="${admin_url}">Admin</a> </security>