Spring Security

Spring Security是为基于Spring的应用程序提供声明式安全保护的安全性框架。Spring Security提供了完整的安全性解决方案。它可以在Web请求级别和方法调用级别处理身份认证和受权。html

Spring Security从两个角度来解决安全性问题。它使用Servlet规范中的Filter保护Web请求并限制URL级别的访问。Spring Security还可以使用Spring AOP保护方法调用——借助于对象代理和使用通知,可以确保只有具有适当权限的用户才能访问安全保护的方法。java

 

Spring Security 3.2 被分红了11个模块web

ACL(access control list)  支持经过访问控制列表为域对象提供安全性正则表达式

切面(Aspects)  一个很小的模块,当使用Spring Security注解时,会使用基于AspectJ的切面,而不是使用标准的Spring AOPspring

CAS客户端(CAS Client)  提供与Jasig的中心认证服务进行集成功能数据库

配置(configuration)  包含经过XML和Java配置Spring Security的功能支持数组

核心(Core)  提供Spring Security基本库浏览器

加密(Cryptography)  提供了加密和密码编码的功能安全

LDAP  支持基于LDAP进行认证服务器

OpenID  支持使用OpenID进行集中式认证

Remoting  提供了对Spring Remoting的支持

标签库(tag library)  Spring Security的JSP标签库

Web  提供了Spring Security基于Filter的Web安全性支持

 

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

Spring Security经过一系列Servlet Filter来提供各类安全性功能。DelegatingFilterProxy是一个特殊的Servlet Filter。它将工做委托给一个javax.servlet.Filter实现类,这个实现类做为一个<bean>注册在Spring应用上下文中。

在web.xml中配置Servlet和Filter

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

经过java方式配置

public class SecurityWebInitializer extends AbstractSecurityWebApplicatuonInitializer{}

AbstractSecurityWebApplicationInitializer实现了WebApplicattionInitialzier,Spring会发现它,并在Web容器中注册DelegatingFilterProxy。

无论咱们用web.xml仍是经过AbstractSecurityWebApplicationInitializer的子类来配置DelegatingFilterProxy,它都会拦截发往应用中的请求,并将请求委托给ID为springSecurityFilterChain的bean。

springSecurityFilterChain自己是另外一个特殊的Filter,他也被称为FilterChainProxy。它能够连接任意一个或多个其余的Filter。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityAdapeter{
}

@EnableWebSecurity注解将会启用Web安全功能,但实际上并无什么用。Spring Security必须配置在一个实现了WebSecurityConfigurer的bean中。

@EnableWebMvcSecurity注解配置了一个Spring MVC参数解析器(argument resolver),这样的话处理器方法可以经过带有@AuthenicationPrincipal注解的参数得到认证用户的principal。它同时还配置了一个bean,在使用Spring报表单绑定标签库来定义表单时,这个bean会自动添加一个隐藏的跨站请求伪造(cross-site request forger, CSRF)token输入域。

 

经过重载WebSecurityConfigurerAdapter中的一个或多个方法(configure)来指定Web安全细节

configure(WebSecurity)  经过重载,配置Spring Security的Filter链

configure(HttpSecurity)  经过重载,配置如何经过拦截器保护请求

configure(AuthenticationManagerBuilder)  经过重载,配置user-detail服务

 

拦截请求:重写protected void configure(HttpSecurity http) throws Exception{}

@Override
protected void configure(HttpSecurity http)  throws Exception{
    http.authorizeRequests().anyRequest().authenticated()
        .and().formLogin().and().httpBasic();
}

上段代码为Spring Security的默认配置。经过调用AuthorizeRequests()和anyRequest().authenticated()就会要求全部进入应用的HTTP请求都要进行认证,同时配置Spring Security支持基于表单的登录以及HTTP Basic方式的认证

@Override
protected void configure(HttpSecurity http) throws Exception{
    http.authorizeRequest()
        .antMathchers("/spitters/me").authenticated() // 对spitters/me请求进行认证
        .antMatchers(HttpMethod.POST, "/spittles").authenticated()在  //对spittles的POST请求进行认证
        .anyRequest().permitAll();  //对于其余全部的请求都是容许的,不须要认证和任何的权限
}

  configure()方法中获得的HttpSecurity对象能够在多个方面配置HTTP的安全性。调用authorizeRequests()方法返回的对象的方法来配置请求级别的安全性细节。

  antMatchers()方法中设定的路径支持Ant风格的通配符。如antMatchers("/spittles/**", "/spittles/mine").authenticated();

  regexMatchers()方法则可以接受正则表达式来定义请求路径。如regexMatchers("/spitters/.*").authenticated();

  authenticated()要求在执行该请求时,必须已经登录了应用,若是用户没有认证的话,Spring Security的Filter将会捕获该请求,并将用户重定向到应用的登录页面。permitAll()方法容许请求没有任何的安全限制。requiresChannel()方法能为各类URL密匙声明所要求的通道。requiresSecure()表示将请求定向到HTTPS上

  e.g:  http.requiresChannel().antMatchers("/spitter/form").requiresSecure();

      http.antMatchers("/").requiresInecure();

 

Spring Security3.2起,默认会开启CSRF(cross-site request forgery)防御。它经过一个同步token的方式来实现CSRF防御的功能。它将会拦截状态变化的请求并检查CSRF token。若是请求中不包含CSRF token或token不能与服务器端的token相匹配,请求会失败,并抛出CsrfException异常。

 

 

 

access(String)  若是给定的SpEL表达式计算结果为true,就容许访问

anonymous()  容许匿名用户访问

authenticated()  容许认证过的用户访问

denyAll()  无条件拒绝全部饭个万宁

fullyAuthenticated()  若是用户是完整证认证的话(不是经过remember-me功能认证的),就容许访问

hasAnyAuthority(String...)  若是用户具有给定权限中的某一个的话,就容许访问

hasAnyRole(String...)  若是用户具有给定角色中的某一个的话,就容许访问

hasAuthhority(String)  若是用户具有给定权限的话,就容许访问

hasIpAddress(String)  若是请求来自给定IP地址的话,就容许访问

hasRole(String)  若是用户具有给定角色的话,就容许访问

not()  对其余访问方法的结果求反

permitAll()  无条件容许访问

rememberMe()  若是用户是经过Remember-me功能认证的,就容许访问

    @Override
    protected void configure(HttpSecurity http) throws Exception{
        http.authorizeRequests()
            .antMatchers("/spitter/ych").hasRole("SPITTER")
            .antMatchers(HttpMethod.POST, "/spittles").hasRole("SPITTER")
            .anyRequest().permitAll();
    }

Spring Security扩展了SpEL以下:

authentication  用户的认证对象

denyAll  结果始终为false

hasAnyRole(list of roles)  若是用户被授予了列表中任意的指定角色,结果为true

hasRole(role)  若是用户被受哦与了指定的角色,结果为true

hasIpAddress(IP Address)  若是请求来自指定IP,结果为true

isAnonymous()  若是当前用户为匿名用户,结果为true

isAuthenticated()  若是当前用户进行认证的话,结果为true

idFullyAuthenticated()  若是当前用户进行了完整认证的话,结果为true

isRememerMe()  若是当前用户是用过Remember-me自动认证的,结果为true

permitALL  始终为true

principal  用户的principal对象

  e.g: antMathcers("/spitter/me").access("hasRole('ROLE_SPITTER') and hasIpAddress('192.168.1.1')")

 

 

用户存储:重写protected void configure(AuthenticationManagerBuilder auth) throws Exception{}

  使用基于内存的用户存储@Override

protected void configure(AuthenticationManagerBuilder auth) throws Exception{
    auth.inMemoryAuthentication().withUser("user").password("password").roles("USER")
        .and().withUser("admin").password("password").roles("USER", "ADMIN");
// auth.inMemoryAuthentication().withUser("user").password("password").authorities("ROLE_USER")
//    .and().withUser("admin").password("password").authorities("ROLE_ADMIN"); }

    经过调用auth.inMemoryAuthentication()启动内存用户存储。withUser()方法为内存用户存储添加新的用户,它返回UserDetailsManagerConfigurer.UserDetailsBuilder。roles()方法是authorities()方法的简写形式。roles()方法所给定的值都会添加一个"ROLE_"前缀,并将其做为权限授予给用户。

                配置用户详细信息的方法

  accountExpired(boolean)  定义帐号是否已通过期

  accountLocked(boolean)  定义帐户是否已经锁定

  and()  用来链接配置

  authorities(GrantedAuthority...)  授予某个用户一项或多项权限 

  authorities(List<? extends GratedAuthority>)  授予某个用户一项或多项权限

  authorities(String...)  授予某个用户一项或多项权限

  credentialsExpired(boolean)  定义凭证是否已通过期

  disabled(boolean)  定义帐号是否已被禁用

  password(String)  定义用户密码

  roles(String...)  授予某个用户一项或多项角色

  

  基于数据库表进行认证

@Autowired
DataSource dataSource;

@Override
protected void configure(AuthenticationManagerBuilder) throws Exception{
    auth.jdbbcAuthentication().dataSource(dataSource);
}

上段代码是默认的最少配置,它对咱们的数据库模式有一些要求,它预期存在某些存储用户数据的表。下面的代码来自于Spring Security的内部:

  public static final String DEF_USERS_BY_USERNAME_QUERY = "select username, password, enabled from users where userame = ?";

  public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY = "select username, authority from authorities where username = ?";

  public static final String DEF_GROUP+AUTHORITIES_BY_USERNAME_QUERY = "select g.id, g.group_name. ga.authority from groups g, group_members gm, group_authorities ga where gm.username = ? and g.id = ga.group+id and g.id = gm.group_id "; 

  能够经过usersByUsernameQuery()和authoritiesByUsernameQuery()来自定义查询

  

@Override

protected void configure(AuthenticationManagerBuilder auth) throws Exception{ auth.jdbcAuthentication().dataSource(dataSource).usersByUsernameQuery("select username, password, true from Spitter where username = ?") .authorititiesByUsernameQuery("select username, 'ROLE_USER' from Spitter where username = ?")
.password(new StandardPasswordEncoder("53cr3t")); }

  将默认的SQL查询替换为自定义的设计时,很重要的一点是要遵循查询的基本协议。全部查询都将用户名做为惟一的参数。认证查询会选取用户名、密码以及启用状态信息。权限查询会选取零行或多行包含该用户名及其权限信息的数据。群组权限查询会选取零行或多行数据,每行数据都会包含群组ID、群组名称以及权限。passwordEncoder()方法能够接受Spring Security中PasswordEncoder接口的任意实现。Spring security的加密模块包括了三个实现:BCryptPasswordEncoder、NoOpPasswordEncoder和StandardPasswordEncoder。

 

  基于LDAP进行认证

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
    auth.jdbcAuthentication().userSearchFilter("{uid={0}}").groupSearchFilter("member={0}");
}

  userSearchFilter()和groupSearchFilter()用来为基础LDAP查询提供过滤条件,它们分别用于搜寻用户和组。默认状况下,对于用户和组的基础查询都是空的,也就是代表搜索会在LDAP层级结构的跟开始。用户能够经过userSearchBase()和groupSearchBase()方法来更改查找用户的基础查询和组指定的基础查询。

@Override
protected void configure(AuthenticationMangagerBuilder auth) throws Exception{
    auth.ldapAuthentication().userSearchBase("ou=people").userSearchFilter("(uid={0})")
        .groupSearchBase("ou=groups").groupSearchFilter("member={0}")
        .passwordCompare();
}

  默认状况下,在登陆表单中提供的密码将会与用户的LDAP条目中的userPassword属性进行对比,若是密码被保存在不一样的属性中们能够经过passwordAttribute()方法来声明密码属性的名称。

  contextSource()方法会返回一个ContextSourceBuilder对象,这个对象提供url()方法来指定LDAP服务器的地址。同时,ContextSourceBuilder还提供root()方法调用Spring Security内嵌的LDAP服务器。当LDAP服务器启动时,它会尝试在类路径下寻找LDIF文件来加载数据。LDIF(LDAP Data Interchange Format)是以文本文件展示LDAP数据的标准方式。每条记录能够有一行或多行,每项包含一个名值对。记录之间经过空行进行分割。若不想Spring从整个根路径下搜索LDIF文件的话,能够经过调用ldif()方法来明确指定加载哪一个LDIF文件。

@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:users.lidf");
}

 

  配置自定义的用户服务

    咱们须要实现UserDetailsService接口并重载loadUserByUsername()方法,而后在congifure中调用。

@Autowired
private SpitterRepository repository;

@Override 
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
    auth.userDetailsService(new SpitterUserService(repository));
}
public class SpitterUserService implements UserDetailsService{

    private final SpitterRepository repository;

    public SpitterUserService(SpittreRepository repository){
        this.repository = repository;
    }

    @Override
    public UserDetais loadUserByUsername(String username) throws UserNameNotFoundException{
        Spitter spitter = repository.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(username + " not found");
    }

}    

 

HTTP Basic认证(Http Basic Authentication)会直接经过HTTP请求自己,对要访问应用程序的用户进行认证。能够经过http.httpBasic()进行开启认证,并调用realmName("Spittr")方法来指定域

 

Spring Security提供Remember-me功能。经过http.rememberMe()来设置。默认状况下,spring会在cookie中存储一个token,这个token最多两周有效,但能够经过设置tokenValiditySecounds(millionseconds)。存储在cookie中的token包含用户名,密码,过时时间和一个私钥,写入cookie前都进行了MD5哈希。默认状况下,私钥名为SpringSecured,但能够经过.key(name)方法更改私钥名称。

退出功能是经过Servlet容器中的Filter实现的,这个Filter会拦截针对"/logout"的请求。当用户访问/logout时,这个请求会被Spring Security的Logout Filter处理。用户会推出应用,全部的remember-me token都会被清除掉。在退出完成后,用户浏览器将会重定向到/login?logout,从而容许用户进行再次登录。若但愿用户被重定向到其余的页面,可使用http.logout().logoutSucessUrl("url")中进行设置,也能够调用.logoutUrl("url")来更改退出路径。

 

使用JSP标签库,须要在JSP中声明<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>

Spring Security的JSP标签库只包含了三个标签

<security:accesscontrollist>  若是用户经过访问控制列表授予了指定的权限,那么渲染该标签体中的内容

<security:authentication>  渲染当前用户认证对象的详细信息

<security:authorize>  若是用户被授予了特定的权限或SpEL表达式的结果为true,那么渲染标签体中的内容

 

使用<security:authentication>JSP标签来访问用户的认证详情

authorites:一组用于表示用户所授予权限的GrantedAuthority对象

Credentials:用于核实用户的凭证(一般是用户名和密码)

details:认证的附加信息(IP地址,证件序列号,会话ID等)

principal:用户的基本信息对象

e.g:  Hello <security:authentication property="principal.username" var="loginId" scope="request" />

 

Spring Security的<security:authorize>JSP标签可以根据用户被授予的权限有条件的渲染页面的部份内容

<sec:authorize access="hasRole('ROLE_SPITTER'')">
    <s:url value="/spittles" var="spittle_url" />
    <sf:form modelAttribute="spittle" action="${spittle_url}">
        <sf:label path="text"><s:message code="lable.spittle" text="Enter spittle:" /></sf:label>
        <sf:textarea path="text" rows="2" cols="40" />
        <sf:errors path="text" />
        <br />
        <div class="spitItSumbitIt">
            <input type="submit" value="Spit it!"  />
        </div>
    </sf:form>
</sec:authorize>

 

Thymeleaf的安全方言提供了与Spring Security标签库相对应的属性。

1.须要在SpringTemplateEngine bean中声明SpringSecurityDialect。

@Bean
public SpringTemplateEngine templateEngine(TemplateResolver templateResolver){
    SpringTemplateEngine templateEngine = new SpringTemplateEngine();
    templateEngine.setTemplateResolver(templateResolver);
    templateEngine.addDialect(new SpringSecurityDialect());
    return templateEngine;
}

2.声明命名空间

  <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">

 

经常使用标签:

sec:authentication  渲染认证对象的属性,相似Spring Security的<sec:authentication/>JSP标签

sec:authorize  基于表达式的计算结果,条件性的渲染内容,相似于Spring Security的<sec:authorize/>JSP标签

sec:authorize-acl  基于表达式的计算结果,条件性渲染内容,相似于Spring Security的<sec:accesscontrollist/>JSP标签

sec:authorize-expr  sec:authorize属性的别名

sec:authorize-url  基于给定URL路径相关的安全规则,条件性的渲染内容,相似于Spring Security的<sec:authorize/>JSP标签使用url属性时的场景

 

Spring Security提供了三种不一样的安全注解:

  Spring Security自带的@Secured注解

  JSR-250的@RolesAllowed注解

  表达式驱动的注解,包括@PreAuthorize、@PostAuthorize、@PreFilter和@PostFilter

  @Secured和@RolesAllowed可以就有用户所授予的权限限制对方法的访问。当须要在方法上定义更加灵活的安全规则时,Spring Security提供了@PreAuthorize和@PostAuthorize,而@PreFilter/@PostFilter可以过滤方法返回的以上传入方法的集合。

 

使用@Secured注解限制方法调用

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends GlobalMethodSecurityConfiguration{

}

  在Spring中,若要启用基于注解的方法安全性,关键要在配置类上使用@EnableGlobalMethodSecurity。若如今Web安全的配置类扩展了WebSecurityConfigurerAdapter,能够重载GlobalMethodSecurityConfiguration的configure()方法。

 

  @Secured注解会使用一个String数组做为参数。每一个String值是一个权限,调用这个方法至少须要具有其中的一个权限。

@Secured告诉Spring,只有具备ROLE_SPITTER权限的认证用户才能调用addSpittke()方法

@Secured("ROLE_SPITTER")
public void addSpittle(Spittle spittle){
}
相关文章
相关标签/搜索