顯示具有 Spring Security 標籤的文章。 顯示所有文章
顯示具有 Spring Security 標籤的文章。 顯示所有文章

2013年1月23日 星期三

Applying Spring Security for SSO of Cross Web Applications

因應雲端世代的來臨,在架構設計上必須讓系統具有分散式佈署的能力,所以將一個大的系統用多個web application來開發是無可避免的方式,除了在module上可以做功能性的分割之外,也可以將不同的web application佈署到不同的application server上,做到server loading分散的目的,只要架設一個Apache HTTP Server做為load balancer,對前端使用者來說就不會感覺到系統後面其實有很多application server在服務。

在這個前提之下,我們會面臨不同web application的session其實是被隔離的問題,也就是說在一個web application登入之後,另外一個web application並不會知道使用者已經登入過,或許會有人用cross servlet context的方式讓不同的web application交換資料,但這牽涉到所使用application server的特性,不見得是好的方法。

我們使用Spring Security的Pre-Authentication機制來處理SSO of Cross Web Applications問題,在某個master web application中實作登入機制,其它的slave web application只需要套用Pre-Authentication機制取得使用者曾經登入的資訊,那麼就不會發生登入不同步的問題了。

以下是我們簡單利用cookie記錄使用者曾經登入過的帳號密碼來做Pre-Authentication,當然帳號、密碼是經過加密的。
Spring XML for Master Web Application
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:security="http://www.springframework.org/schema/security"
    xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <security:http pattern="/login.action" security="none" />
    <security:http pattern="/struts/**" security="none" />
    <security:http pattern="/css/**" security="none" />
    <security:http pattern="/images/**" security="none" />
    <security:http pattern="/js/**" security="none" />
    <security:http pattern="/styles/**" security="none" />
    <security:http pattern="/template/**" security="none" />
    <security:http auto-config="true" authentication-manager-ref="authenticationManager" use-expressions="true">
        <security:form-login authentication-success-handler-ref="cookieTokenAwareAuthenticationSuccessHandler"
            login-page="/login.action" authentication-failure-url="/login.action?login_error=1" />
        <security:logout invalidate-session="true" success-handler-ref="cookieTokenClearingLogoutSuccessHandler"
            logout-url="/spring_security_logout" />
        <security:custom-filter position="PRE_AUTH_FILTER" ref="cookieTokenAuthenticationFilter" />
        <security:custom-filter position="CAS_FILTER" ref="casFilter" />
        <security:intercept-url pattern="/**" access="authenticated" />
    </security:http>

    <security:authentication-manager alias="authenticationManager">
        <security:authentication-provider ref="preauthAuthProvider" />
        <security:authentication-provider ref="casAuthenticationProvider" />
        <security:authentication-provider ref="ldapAuthenticationProvider" />
        <security:authentication-provider user-service-ref="userDetailService">
            <!-- <security:password-encoder ref="passwordEncoder"/> -->
        </security:authentication-provider>
    </security:authentication-manager>

    <!-- <bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.ShaPasswordEncoder"/> -->

    <bean id="cookieTokenAwareAuthenticationSuccessHandler" class="com.gss.gmo.cao.spring.security.web.authentication.CookieTokenAwareAuthenticationSuccessHandler">
        <property name="defaultTargetUrl" value="/init/menu.jsp" />
        <property name="alwaysUseDefaultTargetUrl" value="true" />
    </bean>

    <bean id="cookieTokenClearingLogoutSuccessHandler" class="com.gss.gmo.cao.spring.security.web.authentication.logout.CookieTokenClearingLogoutSuccessHandler">
        <property name="defaultTargetUrl" value="/login.action" />
        <property name="alwaysUseDefaultTargetUrl" value="true" />
    </bean>

    <bean id="cookieTokenAuthenticationFilter" class="com.gss.gmo.cao.spring.security.web.authentication.preauth.CookieTokenAuthenticationFilter">
        <property name="authenticationManager" ref="authenticationManager" />
        <property name="continueFilterChainOnUnsuccessfulAuthentication" value="true" />
        <property name="checkForPrincipalChanges" value="true" />
    </bean>

    <bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
        <property name="preAuthenticatedUserDetailsService" ref="authenticationUserDetailsService" />
    </bean>

    <bean id="authenticationUserDetailsService" class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
        <property name="userDetailsService" ref="userDetailService" />
    </bean>

    <bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties">
        <property name="service" value="http://localhost:8080/portal-web/gss/j_spring_cas_security_check" />
    </bean>

    <bean id="casFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter">
        <property name="filterProcessesUrl" value="/gss/j_spring_cas_security_check" />
        <property name="authenticationManager" ref="authenticationManager" />
        <property name="authenticationSuccessHandler" ref="cookieTokenAwareAuthenticationSuccessHandler" />
        <property name="authenticationFailureHandler">
            <bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
                <property name="defaultFailureUrl" value="/login.action?login_error=1" />
            </bean>
        </property>
    </bean>

    <bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
        <property name="serviceProperties" ref="serviceProperties" />
        <property name="ticketValidator">
            <bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
                <constructor-arg value="http://teamkube.gss.com.tw/cas" />
            </bean>
        </property>
        <property name="key" value="teamkube-cas" />
        <property name="authenticationUserDetailsService" ref="authenticationUserDetailsService" />
    </bean>

</beans>
Spring XML for Slave Web Application
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:security="http://www.springframework.org/schema/security"
    xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <import resource="classpath:spring-acm-core.xml" />
    <import resource="classpath:gmo-cao-struts2-spring-context.xml" />

    <context:annotation-config />
    <context:component-scan base-package="com.gss.acm.web" />

    <security:http pattern="/struts/**" security="none" />
    <security:http pattern="/css/**" security="none" />
    <security:http pattern="/images/**" security="none" />
    <security:http pattern="/js/**" security="none" />
    <security:http pattern="/styles/**" security="none" />
    <security:http pattern="/template/**" security="none" />
    <security:http auto-config="true" use-expressions="true">
        <!-- Additional http configuration omitted -->
        <security:form-login login-page="/../portal-web/login.action" />
        <security:logout invalidate-session="true" logout-success-url="/../portal-web/spring_security_logout"
            logout-url="/spring_security_logout" />
        <security:custom-filter position="PRE_AUTH_FILTER" ref="cookieTokenAuthenticationFilter" />
        <security:intercept-url pattern="/**" access="authenticated" />
    </security:http>

    <security:authentication-manager alias="authenticationManager">
        <security:authentication-provider ref="preauthAuthProvider" />
    </security:authentication-manager>

</beans>
CookieTokenAuthenticationFilter
package com.gss.gmo.cao.spring.security.web.authentication.preauth;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;

import org.jasypt.encryption.StringEncryptor;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.web.util.WebUtils;

import com.gss.gmo.cao.spring.security.util.StringEncryptorFactory;

/**
 * Get username and password from cookie for SSO.
 *
 * @author linus_chien
 *
 */
public class CookieTokenAuthenticationFilter extends AbstractPreAuthenticatedProcessingFilter {

    /**
     * Encryptor.
     */
    private StringEncryptor encryptor = StringEncryptorFactory.createInstance();

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (getPreAuthenticatedPrincipal((HttpServletRequest) request) == null) {
            SecurityContextHolder.clearContext();
        }
        super.doFilter(request, response, chain);
    }

    @Override
    protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
        Cookie passwordCookie = WebUtils.getCookie(request, UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_PASSWORD_KEY);
        if (passwordCookie != null) {
            return encryptor.decrypt(passwordCookie.getValue());
        }
        return null;
    }

    @Override
    protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
        Cookie usernameCookie = WebUtils.getCookie(request, UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_USERNAME_KEY);
        if (usernameCookie != null) {
            return encryptor.decrypt(usernameCookie.getValue());
        }
        return null;
    }

}
CookieTokenAwareAuthenticationSuccessHandler
package com.gss.gmo.cao.spring.security.web.authentication;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.jasypt.encryption.StringEncryptor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.gss.gmo.cao.spring.security.util.StringEncryptorFactory;

/**
 * Set username/password to cookie for cross web context SSO.
 *
 * @author linus_chien
 *
 */
public class CookieTokenAwareAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    /**
     * Encryptor.
     */
    private StringEncryptor encryptor = StringEncryptorFactory.createInstance();
 
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException,
            IOException {
     
        UserDetails user = (UserDetails) authentication.getPrincipal();
        Cookie username = new Cookie(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_USERNAME_KEY, encryptor.encrypt(user.getUsername()));
        username.setPath("/");
        Cookie password = new Cookie(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_PASSWORD_KEY, encryptor.encrypt(user.getPassword()));
        password.setPath("/");

        response.addCookie(username);
        response.addCookie(password);

        super.onAuthenticationSuccess(request, response, authentication);
    }

}
CookieTokenClearingLogoutSuccessHandler
package com.gss.gmo.cao.spring.security.web.authentication.logout;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;

/**
 * Clear username/password from cookie for cross web context SSO.
 *
 * @author linus_chien
 *
 */
public class CookieTokenClearingLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        Cookie username = new Cookie(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_USERNAME_KEY, null);
        username.setPath("/");
        username.setMaxAge(0);
        Cookie password = new Cookie(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_PASSWORD_KEY, null);
        password.setPath("/");
        password.setMaxAge(0);

        response.addCookie(username);
        response.addCookie(password);

        super.onLogoutSuccess(request, response, authentication);
    }

}
最後需要強調的是AbstractPreAuthenticatedProcessingFilter和AbstractAuthenticationProcessingFilter是不相關的filter類別,如果是內部系統彼此間的SSO可以使用AbstractPreAuthenticatedProcessingFilter,但是外部系統的SSO最好還是使用AbstractAuthenticationProcessingFilter的子類別來處理,但無論使用哪種方式,我們都會reuse UserDetailsService instance。

2013年1月22日 星期二

Interview Spring Security



Spring Security的設計如diagram所示:
  1. 每個Filter都會有一個能處理的Authentication type,從request取得資料後生出Authentication instance,然後傳給AuthenticationManager進行驗證。
  2. Spring Security預設只提供一個ProviderManager實作了AuthenticationManager介面。
  3. ProviderManager會依序將Authentication傳給AuthenticationProvider進行驗證。
  4. Spring Security實作的AuthenticationProvider大多都可以傳入UserDetailsService,以方便在通過身分驗證後取得使用者權限,在實作上我們會reuse UserDetailsService instance,如sequence diagram中的UserDetailsServiceImpl就被DaoAuthenticationProvider、LdapAuthenticationProvider、CasAuthenticationProvider所使用。
PS:Sequence diagram省略了org.springframework.security.web.FilterChainProxy和ProviderManager。

Integrate CAS with Spring Security

Spring Security和CAS的整合關係有outgoing和incoming兩個方向:
  1. Outgoing:是指將未經過身分驗證的使用者導到CAS的登入畫面進行身分驗證。
  2. Incoming:是指在CAS已經完成身分認證,然後透過CAS的token機制進行SSO,讓使用者不需要再輸入一次帳號密碼即可進入系統。
一般的系統若是沒有自己的登入入口,就兩者都需要設定,但如果系統本身已經有登入入口,那麼只需要設定第二個SSO的設定就可以了。

以下是在Spring Security設定可以從CAS進行SSO到我們的系統的設定方式,也就是incoming的方向,其中一些handler暫時不多做說明。
Spring XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:security="http://www.springframework.org/schema/security"
    xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <security:http pattern="/login.action" security="none" />
    <security:http pattern="/struts/**" security="none" />
    <security:http pattern="/css/**" security="none" />
    <security:http pattern="/images/**" security="none" />
    <security:http pattern="/js/**" security="none" />
    <security:http pattern="/styles/**" security="none" />
    <security:http pattern="/template/**" security="none" />
    <security:http auto-config="true" authentication-manager-ref="authenticationManager" use-expressions="true">
        <security:form-login authentication-success-handler-ref="cookieTokenAwareAuthenticationSuccessHandler"
            login-page="/login.action" authentication-failure-url="/login.action?login_error=1" />
        <security:logout invalidate-session="true" success-handler-ref="cookieTokenClearingLogoutSuccessHandler"
            logout-url="/spring_security_logout" />
        <security:custom-filter position="PRE_AUTH_FILTER" ref="cookieTokenAuthenticationFilter" />
        <security:custom-filter position="CAS_FILTER" ref="casFilter" />
        <security:intercept-url pattern="/**" access="authenticated" />
    </security:http>

    <security:authentication-manager alias="authenticationManager">
        <security:authentication-provider ref="preauthAuthProvider" />
        <security:authentication-provider ref="casAuthenticationProvider" />
        <security:authentication-provider ref="ldapAuthenticationProvider" />
        <security:authentication-provider user-service-ref="userDetailService">
            <!-- <security:password-encoder ref="passwordEncoder"/> -->
        </security:authentication-provider>
    </security:authentication-manager>

    <!-- <bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.ShaPasswordEncoder"/> -->

    <bean id="cookieTokenAwareAuthenticationSuccessHandler" class="com.gss.gmo.cao.spring.security.web.authentication.CookieTokenAwareAuthenticationSuccessHandler">
        <property name="defaultTargetUrl" value="/init/menu.jsp" />
        <property name="alwaysUseDefaultTargetUrl" value="true" />
    </bean>

    <bean id="cookieTokenClearingLogoutSuccessHandler" class="com.gss.gmo.cao.spring.security.web.authentication.logout.CookieTokenClearingLogoutSuccessHandler">
        <property name="defaultTargetUrl" value="/login.action" />
        <property name="alwaysUseDefaultTargetUrl" value="true" />
    </bean>

    <bean id="cookieTokenAuthenticationFilter" class="com.gss.gmo.cao.spring.security.web.authentication.preauth.CookieTokenAuthenticationFilter">
        <property name="authenticationManager" ref="authenticationManager" />
        <property name="continueFilterChainOnUnsuccessfulAuthentication" value="true" />
        <property name="checkForPrincipalChanges" value="true" />
    </bean>

    <bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
        <property name="preAuthenticatedUserDetailsService" ref="authenticationUserDetailsService" />
    </bean>

    <bean id="authenticationUserDetailsService" class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
        <property name="userDetailsService" ref="userDetailService" />
    </bean>

    <bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties">
        <property name="service" value="http://localhost:8080/portal-web/gss/j_spring_cas_security_check" />
    </bean>

    <bean id="casFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter">
        <property name="filterProcessesUrl" value="/gss/j_spring_cas_security_check" />
        <property name="authenticationManager" ref="authenticationManager" />
        <property name="authenticationSuccessHandler" ref="cookieTokenAwareAuthenticationSuccessHandler" />
        <property name="authenticationFailureHandler">
            <bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
                <property name="defaultFailureUrl" value="/login.action?login_error=1" />
            </bean>
        </property>
    </bean>

    <bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
        <property name="serviceProperties" ref="serviceProperties" />
        <property name="ticketValidator">
            <bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
                <constructor-arg value="http://teamkube.gss.com.tw/cas" />
            </bean>
        </property>
        <property name="key" value="teamkube-cas" />
        <property name="authenticationUserDetailsService" ref="authenticationUserDetailsService" />
    </bean>

</beans>
依照我們的設定,用URL:
http://teamkube.gss.com.tw/cas/login?service=http%3A%2F%2Flocalhost%3A8080%2Fportal-web%2Fgss%2Fj_spring_cas_security_check
就可以用CAS的畫面登入到我們的系統。

2013年1月17日 星期四

Configure LdapAuthenticationProvider Programmatically

LdapAuthenticationProvider基本的設定,將此bean塞到AuthenticationManager中即可。
SpringConfiguration
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.authentication.BindAuthenticator;
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
import com.gss.gmo.cao.spring.security.ldap.userdetails.UserDetailsServiceContextMapper;

@Configuration
public class SpringConfiguration {

    @SneakyThrows
    @Bean
    @Autowired
    LdapAuthenticationProvider ldapAuthenticationProvider(UserDetailsService userDetailsService, @Value("${ldap.server.url}") String serverUrl,
            @Value("${ldap.server.username}") String serverUsername, @Value("${ldap.server.password}") String serverPassword) {

        DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(serverUrl);
        contextSource.setUserDn(serverUsername);
        contextSource.setPassword(serverPassword);
        contextSource.afterPropertiesSet();

        FilterBasedLdapUserSearch filterBasedLdapUserSearch = new FilterBasedLdapUserSearch("OU=GSS,DC=gss,DC=com,DC=tw", "(CN={0})", contextSource);

        BindAuthenticator bindAuthenticator = new BindAuthenticator(contextSource);
        bindAuthenticator.setUserSearch(filterBasedLdapUserSearch);
        bindAuthenticator.afterPropertiesSet();

        LdapAuthenticationProvider ldapAuthenticationProvider = new LdapAuthenticationProvider(bindAuthenticator);
        ldapAuthenticationProvider.setUserDetailsContextMapper(new UserDetailsServiceContextMapper(userDetailsService));
        return ldapAuthenticationProvider;
    }

}
UserDetailsContextMapper的用途是在通過身分驗證之後,可以利用UserDetailsService取得使用者權限。
UserDetailsServiceContextMapper
import java.util.Collection;
import lombok.extern.apachecommons.CommonsLog;
import org.apache.commons.lang.Validate;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
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 org.springframework.security.ldap.userdetails.UserDetailsContextMapper;

/**
 * @author linus_chien
 *
 */
@CommonsLog
public class UserDetailsServiceContextMapper implements UserDetailsContextMapper {

    private UserDetailsService userDetailsService;

    public UserDetailsServiceContextMapper(UserDetailsService userDetailsService) {
        Validate.notNull(userDetailsService);
        this.userDetailsService = userDetailsService;
    }

    @Override
    public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
        try {
            return userDetailsService.loadUserByUsername(username);
        } catch (UsernameNotFoundException e) {
            throw e;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new UsernameNotFoundException(e.getMessage());
        }
    }

    @Override
    public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
        throw new UnsupportedOperationException(
                "UserDetailsServiceContextMapper only supports reading from a context. Please use a subclass if mapUserToContext() is required.");
    }

}

Applying Spring Security for HTTP Basic Authentication of Apache CXF

在這裡記錄如何利用Spring Security設定HTTP Basic Authentication來保護Apache CXF RESTful Service。這裡不討論AuthenticationProvider的實作細節。
Spring XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:jaxrs="http://cxf.apache.org/jaxrs"
    xmlns:security="http://www.springframework.org/schema/security"
    xsi:schemaLocation="http://cxf.apache.org/configuration/security http://cxf.apache.org/schemas/configuration/security.xsd
        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <import resource="spring-cornerstone-logic-core.xml" />

    <context:annotation-config />
    <context:component-scan base-package="com.gss.gmo.cao.cornerstone.logic.web" />

    <!-- Spring Security -->
    <security:http auto-config="false" pattern="/services/rest/**" use-expressions="true">
        <security:http-basic />
        <security:intercept-url pattern="/services/rest/maintain*/**" access="hasRole('MAINTENANCE')" />
        <security:intercept-url pattern="/services/rest/**" access="hasRole('LOOKUP')" />
        <security:intercept-url pattern="/**" access="authenticated" />
    </security:http>

    <security:authentication-manager alias="authenticationManager">
        <security:authentication-provider ref="daoAuthenticationProvider" />
    </security:authentication-manager>

    <!-- Apache CXF -->
    <jaxrs:server id="rest" address="/rest">
        <jaxrs:serviceBeans>
            <ref bean="helloWorld" />
            <ref bean="maintainUnitService" />
            <ref bean="roleGroupService" />
            <ref bean="userService" />
            <ref bean="unitService" />
        </jaxrs:serviceBeans>
        <jaxrs:providers>
            <bean class="com.gss.gmo.cao.restserver.provider.GsonMessageBodyProvider" />
            <bean class="com.gss.gmo.cao.restserver.provider.ThrowableMapper" />
            <bean class="com.gss.gmo.cao.restserver.provider.ExceptionMapper" />
            <bean class="com.gss.gmo.cao.restserver.provider.RuntimeExceptionMapper" />
            <bean class="com.gss.gmo.cao.restserver.provider.ValidationExceptionMapper" />
            <bean class="com.gss.gmo.cao.restserver.provider.StaleStateExceptionMapper" />
        </jaxrs:providers>
    </jaxrs:server>

    <!-- Hibernate Validator -->
    <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
    <bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor" />

</beans>

2013年1月9日 星期三

Spring Security 3.1.x AccessControlListTag 詳解

Spring Security提供了taglibs,讓我們在JSP裡面可以很容易依據權限來決定畫面是否顯示某些重要的區塊。
例如:
代碼:
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec" %>
<%@ taglib uri="/struts-jquery-tags" prefix="sj" %>

<sec:accesscontrollist hasPermission="NORMAL" domainObject="ACM_P_DEPT_QRY">
    <sj:tab id="departmentTab" target="departmentTabDiv" label="%{getText('company.tabs.departmentTab')}" />
</sec:accesscontrollist>
但是這樣還不夠,依據AccessControlListTag的說明,還得在Spring ApplicationContext中提供PermissionEvaluator instance,Spring Security官方提供了兩個Implementing Classes,分別是AclPermissionEvaluatorDenyAllPermissionEvaluator,但通常我們還是自己implement所需要的權限判斷邏輯,而AccessControlListTag裡面只會呼叫以下這個method,另一個method不需要特別實作。
代碼:
boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission)
對了,PermissionEvaluator雖然在Spring Security 3.0就有了,但是AccessControlListTag裡面是3.1之後才開始使用它,這裡所講的方法在Spring Security 3.0並不適用。