Spring实战第九章学习笔记————保护Web应用

保护Web应用

在这一章我们将使用切面技术来探索保护应用程序的方式。不过我们不必自己开发这些切面————我们将使用Spring Security,一种基于Spring AOP和Servlet规范的Filter实现的安全框架。

Spring Security简介

Spring Security是为基于Spring的应用程序提供声明式安全保护的安全性框架。它能够在Web请求级别和方法调用级别处理身份认证和授权。充分利用了依赖注入和面向切面技术。

理解Spring security的模块

模块 描述
ACL 支持通过访问控制列表(ACL)为域对象提供安全性
切面 使用基于AspectJ的切面,而不是使用标准的SpringAOP
CAS客户端 提供与Jasig的中心认证服务(CAS)进行集成的功能
配置(Configuration) 包含通过XML和java配置Spring Security的功能支持
核心(Core) 提供Spring Security基本库
加密 提供了加密和密码编码的功能
LDAP 支持基于LDAP进行认证
OpenID 支持使用OpenID进行集中式认证
Remoting 提供了对Spring Remoting的支持
标签库 Spring Security的JSP标签库
Web 提供了Spring Security基于Filter的Web安全性支持

应用程序的类路径下至少要含有Core和Configuration这两个模块。

过滤Web请求

Spring security借助一系列Servlet Filter来提供各种安全性功能。DelegatingFilterProxy是一个特殊的Servlet Filter,它会将工作委托给一个javax.servlet.Filter实现类,这个实现类作为一个<bean>注册在Spring应用的上下文。如图所示:
image

xml实现:

<filter>
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>
    org.springframework.web.filter.DeleagatingFilterProxy
  </filter-class>
</filter>

在这里,我们把<filter-name>设置成了springSecurityFilterChain。这是因为我们马上将Springsecurity配置在Web安全性中,。DelegatingFilterProxy会把过滤逻辑委托给名为springSecurityFilterChain的Filter bean。

Java配置:

import *;

public class SecurityWebInitializer extends AbstractSecurityWebApplicationInitializer{}

编写简单的安全性配置

启用Web安全性功能的最简单配置

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{

}

@EnableWebSecurity注解将会启用Web安全功能。但它本身并没有什么用处。Springsecurity必须配置在一个实现了WebSecurityConfigurer的bean中或者扩展WebSecurityConfigurerAdapter。最为简单的方法还是刚刚的程序最为简单。

为SpringMVC启用Web安全性能的最简单配置

@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{

}

@EnableWebMvcSecurity注解还配置了一个Spring Mvc参数解析解析器,这样的话处理器方法就能够通过带有@AuthenticationPrincipal租借的参数获得认证用户的principal。

而以上的两个配置会将应用严格锁定,导致没有人能进入该系统了。这是我们要通过重载WebSecurityConfigurerAdapter的三个configure方法来配置Web安全性。

方法 描述
configure(WebSecurity) 通过重载,配置Spring Security的Filter链
configure(HttpSecurity) 通过重载,配置如何通过拦截器保护请求
configure(AuthenticationManagerBuilder) 通过重载,配置user-detail服务

为了让Springsecurity满足我们应用的需求,还需要再添加一点配置。具体来讲我们需要:

  • 配置用户存储;
  • 指定哪些请求需要认证,哪些不需要,以及所需要的权限;
  • 指定一个自定义的登录页面,替代原先默认的登录页。

选择查询用户详细信息的服务

使用基于内存的用户存储

因为我们的安全配置是扩展了WebSecurityConfigurerAdapter,因此配置用户存储的最简单方式就是重载configure()方法,并以AuthenticationManagerBuilder作为传入参数。通过inMemoryAuthentication()方法,我们可以启用配置并任意填充基于内存的用户存储。

@Configuration
@EnableWebMvcSecurity
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");
  }
}
方法 描述
accountExpired(boolean) 定义账号是否已经过期
accountLocked(boolean) 定义账号是否已经锁定
and() 用来连接配置
authorities(GrantedAuthority...) 授予用户一项或多项权限
authorities(List<? extends GrantedAuthority>) 授予用户一项或多项权限
authorities(String...) 授予用户一项或多项权限
credentialExpired(boolean) 定义凭证是否已经过期。
disabled(boolean) 定义账号是否已被禁用
password(boolean) 定义用户的密码
roles(String) 授予用户一项或多项角色

基于数据库列表进行认证

为了配置SpringSecurity使用以JDBC为支撑的用户存储,我们可以使用jdbcAuthentication
()方法,所需的最少配置:

@Autowired
DataSource datasource;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
    auth
    .jdbcAuthentication()
    .dataSource(dataSource)
}
重写默认的用户查询功能
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
    auth
    .jdbcAuthentication()
    .dataSource(dataSource)
    .usersByUsernameQuery(
        "select username,password,true "+
        "from Spitter where username=?")
    .authortiesByUsernameQuery(
        "select username,'ROLE_USER' from Spitter where username=?"    
        );
        )
}

将默认的SQL查询替换为自定义的设计时,很重要得一点是要遵循查询的基本协议。所有的查询都以用户名为唯一的参数。认证查询会选取用户名、密码以及启用状态信息.

使用转码后的密码借助passwordEncoder()方法制定一个密码转码器
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
    auth
    .jdbcAuthentication()
    .dataSource(dataSource)
    .usersByUsernameQuery(
        "select username,password,true "+
        "from Spitter where username=?")
    .authortiesByUsernameQuery(
        "select username,'ROLE_USER' from Spitter where username=?")
    .passwordEncoder(new StandardPasswordEncoder("53cr3t"));
}

配置自定义的用户服务

当我们需要认证的用户存储在非关系型数据库中,我们需要提供一个自定义的UserDetailsService接口实现。

public interface UserDetailsService {
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

我们需要做的就是实现loadUserByUsername()方法

public class SpitterUserService implements UserDetailsService{
    private final SpitterRepository spitterRepository;
    public SpitterUserService(SpitterRepository spitterRepository){
        this.spitterRepository = spitterRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
        Spitter spitter = spitterRepository.findByUsername(username);
        if(spitter !=null){
            List<GrantedAuthority> authorities = 
            new ArrayList<GrantedAuthority>();
            authorities.add(new SimpleGrantedAuthority("ROLE_SPITTER"));

            return new User(
                    spitter.getUsername(),
                    spitter.getPassword(),
                    authorities);

            throw new UsernameNotFoundException(
                "User '" + username + "' not found."
                );
        }
    }
}

为了使用SpitterUserService来认证用户,我们可以通过userDetailsService()方法将其设置到安全配置中:

@Autowired
SpitterRepository spitterRepository;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
    auth
    .userDetailsService(new SpitterUserService(spitterRepository));
}

拦截请求

在应用中不睡所有的请求都需要同等程度的保护,而对每个请求进行安全性控制的关键在于重载configure(HttpSecurity)方法。如下示例:

@Override
protected void configure(HttpSecurity http) throws Exception{
    http
    .authorizeRequests()
    .antMatchers("/spitters/me").authenticated()
    .antMatchers(HttpMethod.POST,"/spittles").authenticated()
    .anyRequest().permitAll();
}

antMatchers()方法用来指定路径。
下表例举了用来定义如何保护路径的配置方法

方法 能够做什么
access(String) 如果给定的SpEL表达式计算结果为true,就允许访问
anonymous() 允许匿名用户访问
anthenticated() 允许认证过的用户访问
denyAll() 无条件拒绝所有访问
fullyAuthenticated() 如果用户是完整认证的,就允许访问
hasAnyAuthority(String…) 如果用户具备给定权限中某一个的话,就允许访问
hasAnyRole(String…) 如果用户具备给定角色中某一个的话,就允许访问
hasAuthority(String) 如果用户具备给定权限的话,就允许访问
hasIpAddress(String) 如果请求来自指定IP地址,就允许访问
hasRole(String) 如果用户具备给定角色的话,就允许访问
not() 对其他访问方法的结果求反
permitAll() 无条件允许访问
rememberMe(String) 如果用户是通过Remember-me功能认证的,就允许访问

使用Spring表达式进行安全保护

Spring Security支持的所有SpEI表达式

安全表达式 计算结果
hasRole([role]) 当前用户是否拥有指定角色。
hasAnyRole([role1,role2]) 多个角色是一个以逗号进行分隔的字符串。如果当前用户拥有指定角色中的任意一个则返回true。
hasAuthority([auth]) 等同于hasRole
hasAnyAuthority([auth1,auth2]) 等同于hasAnyRole
Principle 代表当前用户的principle对象
authentication 直接从SecurityContext获取的当前Authentication对象
permitAll 总是返回true,表示允许所有的
denyAll 总是返回false,表示拒绝所有的
isAnonymous() 当前用户是否是一个匿名用户
isRememberMe() 表示当前用户是否是通过Remember-Me自动登录的
isAuthenticated() 表示当前用户是否已经登录认证成功了。
isFullyAuthenticated() 如果当前用户既不是一个匿名用户,同时又不是通过Remember-Me自动登录的,则返回true。

强制通道的安全性

使用HTTP提交数据是—件具有风险的事情。如果使用HTTP发送无关紧要的信息,这可能不是什么大问题。但是如果你通过HTTP发送诸如密码和信用卡号这样的敏感信息的话,那你就是在找麻烦了。通过HTTP发送的数据没有经过加密,黑客就有机会拦截请求并且能够看到他
们想看的数据。这就是为什么敏感信息要通过HTTPS来加密发送的原因。

传递到configure()方法中的HttpSecurity对象,除了具有authorizeRequests()方法以外,还有一个requiresChannel()方法,借助这个方法能够为各种URL模式声明所要求的通道。

@Override
protected void configure(HttpSecurity http) throws Exception{
  http
    .authorizeRequest()
      .antMatchers("/spitters/me").authenticated()
      .antMatchers(HttpMethod.POST,"/spittles").authenticated()
      .anyRequest().permitAll()
    .and()
    .requiresChannel()
      .antMatchers("/spitter/form").requiresSecure();//需要HTTPS
}  

防止跨站请求伪造

跨站请求伪造(cross-site request forgery,CSRF)的—个简单样例。简单来讲,如果—个站
点欺骗用户提交请求到其他服务器的话,就会发生跨站请求伪造(cross-site request forgery,CSRF)攻击,这可能会带来消极的后果。

Spring Security通过—个同步token的方式来实现CSRF防护的功能。它将会拦截状态变化的请求(例如,非GET、HEAD、OPTIONS和TRACE的请求)并检查CSRF
token。如果请求中不包含CSRF token的话,或者token不能与服务器端的token相匹配,请求将会失败,并抛出
CsrfException异常。这意味着在你的应用中,所有的表单必须在—个" csrf"域中提交token,而且这个token必须
要与服务器端计算并存储的token—致,这样的话当表单提交的时候,才能进行匹配。
Spring Security巳经简化了将token放到请求的属性中这—任务。如果使用JSP作为页面模板的话:

<input type="hidden"
    name="${_csrf.parameterName}"
    value="${_csrf.token}"/>

禁用SpringSecurity的CSRF防护功能:

@Override
protected void configure(HttpSecurity http) throws Exception{
    http
    ```
    .csrf()
    .disabled();
}

认证用户

启用HTTP BASIC认证

HTTP Basic认证(HTTP Basic Authentication)会直接通过HTTP请求本身,对要访问应用程序的用户进行认证。你可能在以前见过HTTP
Basic认证。当在Web浏览器中使用时,它将向用户弹出—个简单的模态对话框。
但这只是Web浏览器的显示方式。本质上,这是—个HTTP 401响应,表明必须要在请求中包含
—个用户名和密码。在REST客户端向它使用的服务进行认证的场景中,这种方式比较适合。

如果要启用HTTP Basic认证的话,只需在configure()方法所传入的HttpSecurity对象上调用httpBasic()即可。另外,还可以通过调用realmName()方法指定域。如下是在 Spring Security中启用HTTP Basic认证的典型配置:

@Override
protected void configure(HttpSecurity http) throws Exception{
    http
    .formLogin()
    .loginPage("/login")
    .and()
    .httpBasic()
    .realmName("Spittr")
    .and()
    ```
}

启用Remember-me功能

站在用户的角度来讲,如果应用程序不用每次都提示他们登录是更好的。这就是为什么许多站点提供了Remember-me功能,你只要登录过一次,应用就会记住你,当再次回到应用的时候你就不需要登录了。

@Override
protected void configure(HttpSecurity http) throws Exception{
    http
    .formLogin()
    .loginPage("/login")
    .and()
    .rememberMe()
    .tokenvaliditySeconds(2419200)
    .key("spitterkey")
    ```
}

在这里,我们通过—点特殊的配笸就可以启用Remember-me功能。默认情况下,这个功能是通
过在cookie中存储—个token完成的,这个token最多两周内有效。但是,在这里,我们指定这个
token最多四周内有效(2,419,200秒)。

保护视图

小结

对于许多应用而言,安全性都是非常重要的切面。Spring Security提供了—种简单、灵活且强大
的机制来保护我们的应用程序。

借助于—系列Servlet Filter,Spring Security能够控制对Web资源的访问,包括Spring MVC控制器。借助于Spring
Security的Java配笸模型,我们不必直接处理Filter,能够非常简沽地声明Web安全性功能。
当认证用户时,Spring Security提供了多种选项。我们探讨了如何基于内存用户库、关系型数据
库和LDAP目录服务器来配笸认证功能。如果这些可选方案无法满足认证需求的话,我们还学
习了如何创建和配笸自定义的用户服务。

猜你喜欢

转载自www.cnblogs.com/wbw2621/p/9641079.html