Spring Security

Spring Security

spring security

基本Spring Security

最新版本官方详细文档 讲解了所有Spring Security细节

XML配置示例

直接上例子。

<http pattern="/admin/**" auto-config="true" use-expressions="true" authentication-manager-ref="userAuthenticationManager">  
        <intercept-url pattern="/admin/logon.html*" access="permitAll" />
        <intercept-url pattern="/admin/denied.html" access="permitAll" />
        <intercept-url pattern="/admin/unauthorized.html" access="permitAll" />
        <intercept-url pattern="/admin/users/resetPassword.html*" access="permitAll" />
        <intercept-url pattern="/admin/users/resetPasswordSecurityQtn.html*" access="permitAll" /> 
        <intercept-url pattern="/admin" access="hasRole('AUTH')" />
        <intercept-url pattern="/admin/" access="hasRole('AUTH')" />
        <intercept-url pattern="/admin/*.html*" access="hasRole('AUTH')" />
        <intercept-url pattern="/admin/*/*.html*" access="hasRole('AUTH')" />
        <intercept-url pattern="/admin/*/*/*.html*" access="hasRole('AUTH')" />


        <form-login 
            login-processing-url="/admin/j_spring_security_check" 
            login-page="/admin/logon.html"
            authentication-success-handler-ref="userAuthenticationSuccessHandler"
            authentication-failure-url="/admin/logon.html?login_error=true"
            default-target-url="/admin/home.html" />


        <logout invalidate-session="true"
            logout-success-url="/admin/home.html"
            logout-url="/admin/j_spring_security_logout" />
        <access-denied-handler ref="adminAccessDenied"/>
    </http>



    <http pattern="/shop/**" auto-config="true" use-expressions="true" authentication-manager-ref="customerAuthenticationManager">

        <intercept-url pattern="/shop" access="permitAll" />
        <intercept-url pattern="/shop/" access="permitAll" />
        <intercept-url pattern="/shop/**" access="permitAll" />
        <intercept-url pattern="/shop/customer/logon.html*" access="permitAll" />
        <intercept-url pattern="/shop/customer/registration.html*" access="permitAll" />
        <intercept-url pattern="/shop/customer/customLogon.html*" access="permitAll" />
        <intercept-url pattern="/shop/customer/denied.html" access="permitAll" />
        <intercept-url pattern="/shop/customer/j_spring_security_check" access="permitAll" />
        <intercept-url pattern="/shop/customer" access="hasRole('AUTH_CUSTOMER')" />
        <intercept-url pattern="/shop/customer/" access="hasRole('AUTH_CUSTOMER')" />
        <intercept-url pattern="/shop/customer/*.html*" access="hasRole('AUTH_CUSTOMER')" />
        <intercept-url pattern="/shop/customer/*.html*" access="hasRole('AUTH_CUSTOMER')" />
        <intercept-url pattern="/shop/customer/*/*.html*" access="hasRole('AUTH_CUSTOMER')" />
        <intercept-url pattern="/shop/customer/*/*/*.html*" access="hasRole('AUTH_CUSTOMER')" />


        <logout invalidate-session="false" 
            logout-success-url="/shop/" 
            logout-url="/shop/customer/j_spring_security_logout" />
        <access-denied-handler error-page="/shop/"/>
    </http>


    <!-- REST services -->
    <http pattern="/services/**" create-session="stateless" use-expressions="true" authentication-manager-ref="userAuthenticationManager" entry-point-ref="servicesAuthenticationEntryPoint">
      <!--<intercept-url pattern="/services/private/**" access="hasRole('AUTH')"/>-->
      <intercept-url pattern="/services/private/**" access="hasRole('AUTH')"/>
      <intercept-url pattern="/services/public/**" access="permitAll"/>
      <form-login authentication-success-handler-ref="servicesAuthenticationSuccessHandler" />
      <http-basic />
    </http>

示例配置来源 老外开源电商项目shopizer。分为前台后台和Rest接口。很好的开源项目,感谢作者。 开始会有很多疑问,比如,http标签代表了什么?http便签属性,内部元素都代表了什么,哪些是必备的,哪些是可选的,如何不用系统默认的使用自定义?

每一个http标签会创建一个FilterChain,过滤器链,也就是多种过滤器的一个有序集合,并且它们会按照一定的顺序被调用,顺序非常重要。可以有多个http标签,示例已经演示。第一个http标签代表了一个后台登录,退出,记住我,以及后台权限控制的所有模块。多个http编辑配置有个规则,匹配度越明确的越细的放在上边,匹配度越模糊越粗的放后边,因为多个FilterChain也有一个顺序,就是XML的定义的先后,如果前一个定义为/services/*,后一个定义为/services/user,当有url为/services/user的请求进入时,会首先匹配到第一个,只要有一个匹配成功,下文便不再匹配。这就是 http 的pattern属性。其实http便签(FilterChain)不仅仅能按请求中的url匹配,pattern匹配对应的Java类是AntPathRequestMatcher,还可以自定义任何匹配。看一下matcher包下一个简单的匹配器RequestHeaderRequestMatcher,自己可以照葫芦画瓢。 RequestHeaderRequestMatcher

package org.springframework.security.web.util.matcher;

import javax.servlet.http.HttpServletRequest;

import org.springframework.security.web.util.matcher.RequestMatcher;  
import org.springframework.util.Assert;

public final class RequestHeaderRequestMatcher implements RequestMatcher {  
    private final String expectedHeaderName;
    private final String expectedHeaderValue;

    public RequestHeaderRequestMatcher(String expectedHeaderName) {
        this(expectedHeaderName, null);
    }
    //匹配你想要的header的键 或 键和值
    public RequestHeaderRequestMatcher(String expectedHeaderName,
            String expectedHeaderValue) {
        Assert.notNull(expectedHeaderName, "headerName cannot be null");
        this.expectedHeaderName = expectedHeaderName;
        this.expectedHeaderValue = expectedHeaderValue;
    }

    public boolean matches(HttpServletRequest request) {
        String actualHeaderValue = request.getHeader(expectedHeaderName);
        if (expectedHeaderValue == null) {
            return actualHeaderValue != null;
        }

        return expectedHeaderValue.equals(actualHeaderValue);
    }

    @Override
    public String toString() {
        return "RequestHeaderRequestMatcher [expectedHeaderName="
                + expectedHeaderName + ", expectedHeaderValue="
                + expectedHeaderValue + "]";
    }
}

matches 方法实现自RequestMatcher接口。简单至极。
如何让自定义的Matcher Bean setter到FilterChain里边,request-matcher-ref 这个属性指定bean的引用。

AuthenticationManager

说了匹配,下一个,先说重要的后说次要的 authentication-manager-ref 重中之重,指定一个AuthenticationManager的引用。AuthenticationProvider UserDetailService UserDetail这几个关联的概念会一起说。表明概念很多,其实很清晰,按英文意思都能猜功能出来。

示例代码

<authentication-manager id="userAuthenticationManager">  
        <authentication-provider user-service-ref="userDetailsService">
            <password-encoder hash="sha" />
        </authentication-provider>
</authentication-manager>  

UserDetailService的实现类

package com.salesmanager.web.admin.security;  
/**
 * 
 * @author casams1
 *         http://stackoverflow.com/questions/5105776/spring-security-with
 *         -custom-user-details
 */
@Service("userDetailsService")
public class UserServicesImpl implements WebUserServices{

    private static final Logger LOGGER = LoggerFactory.getLogger(UserServicesImpl.class);

    @Autowired
    private UserService userService;

    @Autowired
    private MerchantStoreService merchantStoreService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    protected PermissionService  permissionService;

    @Autowired
    protected GroupService   groupService;

    @SuppressWarnings("deprecation")
    public UserDetails loadUserByUsername(String userName)
            throws UsernameNotFoundException, DataAccessException {

        com.salesmanager.core.business.user.model.User user = null;
        Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();

        try {
            user = userService.getByUserName(userName);
            if(user==null) {
                return null;
            }
            GrantedAuthority role = new GrantedAuthorityImpl(Constants.PERMISSION_AUTHENTICATED);//required to login
            authorities.add(role);
            List<Integer> groupsId = new ArrayList<Integer>();
            List<Group> groups = user.getGroups();
            for(Group group : groups) {
                groupsId.add(group.getId());
            }

            List<Permission> permissions = permissionService.getPermissions(groupsId);
            for(Permission permission : permissions) {
                GrantedAuthority auth = new GrantedAuthorityImpl(permission.getPermissionName());
                authorities.add(auth);
            }

        } catch (Exception e) {
            LOGGER.error("Exception while querrying user",e);
            throw new SecurityDataAccessException("Exception while querrying user",e);
        }

        User secUser = new User(userName, user.getAdminPassword(), user.isActive(), true,
                true, true, authorities);
        return secUser;
    }
}

AuthenticationManager官网解释,AuthenticationManager本身只是个接口,在Spring Security默认实现类是ProviderManager。AuthenticationManager认证管理器本身并不处理认证请求,只是委派给AuthenticationProviders去验证(官网提到可以多个provider,每个provider按顺序验证请求,每个provider有三种情况,不验证(不归自己管)return null,验证失败 throw Exception,通过返回认证信息,只要有一个通过便是通过,(这里的通过跟AccessDecisionManager中的Voter不是一回事)多种provider暂且不论)。provider根据什么验证,便是UserDetailService,UserDetailService继承自是个接口,只有一个方法,loadUserByUsername,返回一个UserDetail。UserDetailService的示例代码中,自定义UserDetailService实现了loadUserByname,示例中的User是实现了UserDetail接口的User,loadUserByname中,数据库查询出User,并将User的权限包装上。 在这里需要补充一下,密码加密中常见的盐,Spring Security并不是默认的,需要自己实现。参考stack overflow 用ReflectionSaltSource这个SaltSource的实现类,需要制定salt的属性名,通过ReflectionSaltSource中的userPropertyToUse,比如设置成“salt”,会用反射的方法从UserDeatail中取出salt ,(getSalt()或者salt())取出盐,如果UserDetail没有该方法,会出现方法找不到,需要在UserDetail实现类里边加上两个方法任一。 文档1 文档2

<password-encoder hash="sha">  
    <salt-source user-property="salt"/>
  </password-encoder>

intercept-url

http-basic http-basic标签做了什么,Adds a BasicAuthenticationFilter and BasicAuthenticationEntryPoint to the configuration。

form-login form-login标签做了什么,往FilterChain里边加了一个UsernamePasswordAuthenticationFilter,还有一个LoginUrlAuthenticationEntryPoint,AuthenticationEntryPoint是个什么东西,文档(暂时可略过),(AuthenticationEntryPoint是个不好理解的东西,基本的Web可能用不到,Rest OAuth可能会用到,字面意思是入口点,字面意思不足以理解,官方文档说是个处理异常操作的地方,比如登录密码错误调回登录页)。

BasicAuthenticationEntryPoint源码,commence方法继承自接口AuthenticationEntryPoint,只看commence方法内好像没有太复杂,首先要了解http-basic.

public class BasicAuthenticationEntryPoint implements AuthenticationEntryPoint, InitializingBean {  
    //~ Instance fields ================================================================================================

    private String realmName;

    //~ Methods ========================================================================================================

    public void afterPropertiesSet() throws Exception {
        Assert.hasText(realmName, "realmName must be specified");
    }

    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
            throws IOException, ServletException {
        response.addHeader("WWW-Authenticate", "Basic realm=\"" + realmName + "\"");
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
    }

    public String getRealmName() {
        return realmName;
    }

    public void setRealmName(String realmName) {
        this.realmName = realmName;
    }

}

custom-filter

标准Filter顺序,自定义Filter

Standard Filter Aliases and Ordering

AccessDecisionManager 文档1文档2

访问决定管理器,来决定一个拦截的请求是否可以通过。默认hasRole("role1","role2"),只要有一个role通过就可以,其实是有默认的规则,也就是默认的AccessDecisionManager,AffirmativeBased,所在包下有多种规则。默认的AffirmativeBased支持两个Voter,RoleVoter AuthenticatedVoter。(如果实现OAuth2.0,肯还会多加一个Voter,ScopeVoter,这已经不是SpringSecurity jar包下的,是SpringSecurityOAuth jar包下)

自定义AccessDecisionManager 源码来源 li.shengzhao / spring-oauth-server

<bean id="oauth2AccessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased">  
        <constructor-arg>
            <list>
                <bean class="org.springframework.security.oauth2.provider.vote.ScopeVoter"/>
                <bean class="org.springframework.security.access.vote.RoleVoter"/>
                <bean class="org.springframework.security.access.vote.AuthenticatedVoter"/>
            </list>
        </constructor-arg>
    </bean>
Http标签 use-expressions
use-expressions="false"

默认false, intercept-url access 可选项 AuthenticatedVoter ROLEUSER,ROLECLIENT(OAuth),ROLE_ANONYMOUS 我找不到具体的文档。

use-expressions="true"

intercept-url access 可选项 SecurityExpressionRoot

use-expressions="true"时更加灵活强大,因为可以使用SpEL,Expression-Based Access Control

基于xml的http标签跟基于Annotation的Config.class配置有什么相同与不同。


未完待续。。。

Related Article