Spring security

我的SpringSecurity实践

数据库与实体类设计(mysql)

 

Sql代码

-- 权限
DROP TABLE IF EXISTS `me`.`tbl_permission` ;

CREATE  TABLE IF NOT EXISTS `me`.`tbl_permission` (
  `_id` INT NOT NULL AUTO_INCREMENT ,
  `_name` VARCHAR(45) NOT NULL ,
  `_desc` VARCHAR(255) NULL ,
  PRIMARY KEY (`_id`) ,
  UNIQUE INDEX `_name_UNIQUE` (`_name` ASC) )
ENGINE = InnoDB;

-- 角色
DROP TABLE IF EXISTS `me`.`tbl_role` ;

CREATE  TABLE IF NOT EXISTS `me`.`tbl_role` (
  `_id` INT NOT NULL AUTO_INCREMENT ,
  `_name` VARCHAR(45) NOT NULL ,
  PRIMARY KEY (`_id`) ,
  UNIQUE INDEX `_name_UNIQUE` (`_name` ASC) )
ENGINE = InnoDB;

-- 角色权限关联表
DROP TABLE IF EXISTS `me`.`tbl_role_permission` ;

CREATE  TABLE IF NOT EXISTS `me`.`tbl_role_permission` (
  `_id_permission` INT NOT NULL ,
  `_id_role` INT NOT NULL ,
  PRIMARY KEY (`_id_permission`, `_id_role`) ,
  INDEX `fk_permission_QENOQPN` (`_id_permission` ASC) ,
  INDEX `fk_role_PQEMAQWENGHJ` (`_id_role` ASC) ,
  CONSTRAINT `fk_permission_QENOQPN`
    FOREIGN KEY (`_id_permission` )
    REFERENCES `me`.`tbl_permission` (`_id` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_role_PQEMAQWENGHJ`
    FOREIGN KEY (`_id_role` )
    REFERENCES `me`.`tbl_role` (`_id` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;

-- 用户表
DROP TABLE IF EXISTS `me`.`tbl_user` ;

CREATE  TABLE IF NOT EXISTS `me`.`tbl_user` (
  `_id` INT NOT NULL AUTO_INCREMENT ,
  `_username` VARCHAR(45) NOT NULL ,
  `_password` CHAR(32) NULL ,
  `_disabled` CHAR(1) NULL ,
  PRIMARY KEY (`_id`) ,
  UNIQUE INDEX `_username_UNIQUE` (`_username` ASC) ,
  UNIQUE INDEX `_disabled_UNIQUE` (`_disabled` ASC) )
ENGINE = InnoDB;

-- 用户Email表 跟SpringSecurity没有关系
DROP TABLE IF EXISTS `me`.`tbl_email` ;

CREATE  TABLE IF NOT EXISTS `me`.`tbl_email` (
  `_user_id` INT NOT NULL ,
  `_email` VARCHAR(70) NOT NULL ,
  `_order` INT NOT NULL ,
  PRIMARY KEY (`_user_id`, `_email`) ,
  UNIQUE INDEX `_email_UNIQUE` (`_email` ASC) ,
  INDEX `fk_user_id_QWZLAKUIG` (`_user_id` ASC) ,
  CONSTRAINT `fk_user_id_QWZLAKUIG`
    FOREIGN KEY (`_user_id` )
    REFERENCES `me`.`tbl_user` (`_id` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;

-- 用户角色关联表
DROP TABLE IF EXISTS `me`.`tbl_user_role` ;

CREATE  TABLE IF NOT EXISTS `me`.`tbl_user_role` (
  `_user_id` INT NOT NULL ,
  `_role_id` INT NOT NULL ,
  PRIMARY KEY (`_user_id`, `_role_id`) ,
  INDEX `fk_user_id_POIUYHJNB` (`_user_id` ASC) ,
  INDEX `fk_role_id_MNHTRFVD` (`_role_id` ASC) ,
  CONSTRAINT `fk_user_id_POIUYHJNB`
    FOREIGN KEY (`_user_id` )
    REFERENCES `me`.`tbl_user` (`_id` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_role_id_MNHTRFVD`
    FOREIGN KEY (`_role_id` )
    REFERENCES `me`.`tbl_role` (`_id` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB;


显然这种设计方式实际上比较常见的,用户与角色是多对多关系;角色与权限也是多对多关系。

下面是实体类(为了节约篇幅getter和setter以及ORM相关的元注释略去)

 

显然这种设计方式实际上比较常见的,用户与角色是多对多关系;角色与权限也是多对多关系。

下面是实体类(为了节约篇幅getter和setter以及ORM相关的元注释略去)

import java.io.Serializable;
import java.util.Set;
import org.springframework.security.core.GrantedAuthority;

/** 权限类实现 GrantedAuthority接口 */
public class Permission implements Serializable, GrantedAuthority {
	private Integer id;
	private String name;
	private String description;
	private Set<Role> roles;

	public String getAuthority() {
		return getName();
	}
}


 

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import org.springframework.security.core.GrantedAuthority;

/** 角色类 */
public class Role implements Serializable {
	private Integer id;
	private String name;
	private Set<Permission> permissions = new HashSet<Permission>();
	private Set<User> users = new HashSet<User>();
	
	// 为了简便起见 ROLE 和 Permission都视为一种权限
	public GrantedAuthority generateGrantedAuthority() {
		return new GrantedAuthority() {
			public String getAuthority() {
				return getName();
			}
		};
	}
}


 

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

/** 用户类实现UserDetails接口 */
public class User implements Serializable, UserDetails {
	private Integer id;
	private String username;
	private String password;
	private List<String> emails = new ArrayList<String>();	// 和SpringSecurity没有关系的业务字段
	private String disabled;
	private Set<Role> roles = new HashSet<Role>();

	public Collection<? extends GrantedAuthority> getAuthorities() {
		List<GrantedAuthority> list = new ArrayList<GrantedAuthority>();
		
		for (Role role : roles) {
			list.add(role.generateGrantedAuthority());
			for (Permission permission : role.getPermissions()) {
				list.add(permission);
			}
		}
		// 排序其实没有必要
		// Collections.sort(list, GrantedAuthorityComparators.REVERSE);
		return list;
	}

	public String getPassword() {
		return password;
	}

	public String getUsername() {
		return username;
	}

	public boolean isAccountNonExpired() {
		return true;
	}

	public boolean isAccountNonLocked() {
		return true;
	}

	public boolean isCredentialsNonExpired() {
		return true;
	}

	public boolean isEnabled() {
		return disabled.equalsIgnoreCase("F");
	}
}

class GrantedAuthorityComparators implements Comparator<GrantedAuthority> {
	
	public static final Comparator<GrantedAuthority> DEFAULT = new GrantedAuthorityComparators();
	
	public static final Comparator<GrantedAuthority> REVERSE = new Comparator<GrantedAuthority>() {
		public int compare(GrantedAuthority o1, GrantedAuthority o2) {
			return - DEFAULT.compare(o1, o2);
		}
	};

	private GrantedAuthorityComparators() { super(); }

	public int compare(GrantedAuthority g1, GrantedAuthority g2) {
		return g1.getAuthority().compareTo(g2.getAuthority());
	}
}


 

编写重要的UserDetailsService实现类

import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.yingzhuo.me.domain.User;

@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public interface UserDao extends UserDetailsService {
	public User findUserByUsernameAndPassword(String username, String password);
}


 

import org.hibernate.Query;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Repository;

@Repository("userDao")
public class UserDaoImpl extends BaseDao implements UserDao {

	public User findUserByUsernameAndPassword(String username, String password) {
		final String hql = "from User as u left join fetch u.roles where u.username = :username and u.password = :password";

		Query query = getSession().createQuery(hql)
				.setParameter("username", username)
				.setParameter("password", password);
		return (User) query.uniqueResult();
	}

	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		final String hql = "from User as u left join fetch u.roles where u.username = :username";

		Query query = getSession().createQuery(hql).setParameter("username", username);
		User user = (User) query.uniqueResult();

		if (user == null) {
			throw new UsernameNotFoundException("Username '" + username + "' not found");
		}

		// 取消延迟加载
		for (Role role : user.getRoles()) {
			for (Permission per : role.getPermissions()) {
				per.getName();
			}
		}
		return user;
	}
}


 

虽然SpringSecurity框架提供的接口很多,真正要亲自实现的不多三个而已

  • org.springframework.security.core.GrantedAuthority
  • org.springframework.security.core.userdetails.UserDetails
  • org.springframework.security.core.userdetails.UserDetailsService



 编写Spring Security 配置文件
尽管SpringSecurity提供了<http>元素来简化配置,简化过后有相当多的细节被隐藏起来了。
有时候想改变一下框架的默认行为十分不便。我还是用传统的Bean方式。
这样虽然麻烦,但是掌握之后,一旦看哪个bean不顺眼就可以方便的取而代之。强大灵活!

3.0 xml的schema, 因为是用bean的方式配置嘛,默认命名空间当然是用beans方便。

<?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:p="http://www.springframework.org/schema/p"
	xmlns:c="http://www.springframework.org/schema/c"
	xmlns:util="http://www.springframework.org/schema/util"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:security="http://www.springframework.org/schema/security"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
		http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">
</beans>


 

web项目部署描述

<!-- Spring Security 核心拦截器组 -->
<filter>
	<filter-name>springSecurityFilterChain</filter-name>
	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
	<filter-name>springSecurityFilterChain</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

<!-- Web Application Context -->
<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>
		classpath:spring-bean.xml
		classpath:spring-orm.xml
		classpath:spring-security.xml
	</param-value>
</context-param>

<!-- 支持SpringSecurity Session并发控制 -->
<listener>
	<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>


 

提醒一下,如果SpringSecurity和Struts2共同使用的话 org.springframework.web.filter.DelegatingFilterProxy 一定要配置在
org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter前面。要不然你的SpringSecurity根本不会起任何作用的。
当然如果使用的是 SpringMVC框架的话那根本无所谓配置顺序,因为SpringMVC的核心转发器是一个Servlet的实现。


3.2 配置SpringSecurity过滤器组 (我一般就用这九个) 顺序很重要

<bean id="springSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy">
	<security:filter-chain-map request-matcher="ant" >
	<security:filter-chain pattern="/**" filters="
	   channelProcessingFilter,
           concurrencyFilter,
           securityContextPersistenceFilter,
           logoutFilter,
           usernamePasswordProcessingFilter,
           rememberMeProcessingFilter,
           anonymousProcessingFilter,
           exceptionTranslationFilter,
           filterSecurityInterceptor" />
	</security:filter-chain-map>
</bean>


channelProcessingFilter: 常用来将某些HTTP协议的URL重定向到HTTPS协议

<bean id="channelProcessingFilter" class="org.springframework.security.web.access.channel.ChannelProcessingFilter">
	<property name="channelDecisionManager" ref="channelDecisionManager" />
	<property name="securityMetadataSource">
		<security:filter-security-metadata-source request-matcher="ant">
			<!--
			<security:intercept-url pattern="/just/test" access="REQUIRES_SECURE_CHANNEL" />
			-->
			<security:intercept-url pattern="/**" access="ANY_CHANNEL" />
		</security:filter-security-metadata-source>
	</property>
</bean>

<bean id="channelDecisionManager"
	class="org.springframework.security.web.access.channel.ChannelDecisionManagerImpl">
	<property name="channelProcessors">
		<list>
			<ref local="secureChannelProcessor" />
			<ref local="insecureChannelProcessor" />
		</list>
	</property>
</bean>

<bean id="secureChannelProcessor" class="org.springframework.security.web.access.channel.SecureChannelProcessor" />
<bean id="insecureChannelProcessor" class="org.springframework.security.web.access.channel.InsecureChannelProcessor" />


concurrencyFilter:HttpSession并发过滤器

<bean id="concurrencyFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter">
	<property name="sessionRegistry" ref="sessionRegistry" />
	<property name="expiredUrl" value="/common/session-expired" />	<!-- 配置Session过期后重定向的地址 -->
</bean>

<bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" />

<bean id="sas" class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">
	<constructor-arg index="0" ref="sessionRegistry" />
	<property name="maximumSessions" value="1" />
</bean>


securityContextPersistenceFilter:获取或存储一个SecurityContext

<bean id="securityContextPersistenceFilter" class="org.springframework.security.web.context.SecurityContextPersistenceFilter" />


logoutFilter:监控一个实现退出功能的URL

<bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
	<constructor-arg index="0" value="/common/login" />	<!-- 退出后重定向 -->
	<constructor-arg index="1">
		<array>
			<ref local="logoutHandler" />
			<ref local="rememberMeServices" />
		</array>
	</constructor-arg>
	<property name="filterProcessesUrl" value="/common/logout"/> <!-- 监控的URL -->
</bean>

<!-- 这个Bean注入到logoutFilter中去,它实际负责最后的扫尾工作,如把HttpSession实例删除 -->
<bean id="logoutHandler" class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler">
	<property name="invalidateHttpSession" value="true" />
</bean>


这个bean没有没有默认构造方法。

3.7 usernamePasswordProcessingFilter:处理用户登录请求

<bean id="usernamePasswordProcessingFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
	<property name="filterProcessesUrl" value="/common/login-process"/>
	<property name="usernameParameter" value="username"/>
	<property name="passwordParameter" value="password"/>
	<property name="authenticationManager" ref="customAuthenticationManager"/>
	<property name="rememberMeServices" ref="rememberMeServices"/>
	<property name="authenticationFailureHandler" ref="authenticationFailureHandler"/>
	<property name="sessionAuthenticationStrategy" ref="sas" />
</bean>

<!--
	这个Bean注入到usernamePasswordProcessingFilter中去,他决定用户名和密码验证失败之后的动作
	注意: 应设置行为为转发方式,否则保存在HttpServletRequest实例中的错误信息会因为重定向而丢失。
 -->
<bean id="authenticationFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
	<property name="defaultFailureUrl" value="/common/login"/>
	<property name="useForward" value="true" />
</bean>


rememberMeProcessingFilter: 实现"记住我"功能

<bean id="rememberMeProcessingFilter" class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
	<property name="rememberMeServices" ref="rememberMeServices"/>
	<property name="authenticationManager" ref="customAuthenticationManager" />
</bean>

<bean id="rememberMeServices" class="org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices">
	<property name="key" value="#{securityKeys['remember-me']}" />	<!-- KEY 用于加密 两个一定要相同 -->
	<property name="parameter" value="_remember_me" />
	<property name="tokenValiditySeconds" value="7200" />
	<property name="tokenRepository" ref="inMemoryTokenRepository" />
	<!-- 下面就是我自己实现的UserDetailsService 我给了alias "hibernateUserDetailsService" -->
	<property name="userDetailsService" ref="hibernateUserDetailsService" />
</bean>

<bean id="rememberMeAuthenticationProvider"
	class="org.springframework.security.authentication.RememberMeAuthenticationProvider">
	<property name="key" value="#{securityKeys['remember-me']}" /> <!-- KEY 用于加密 两个一定要相同 -->
</bean>

<bean id="inMemoryTokenRepository" class="org.springframework.security.web.authentication.rememberme.InMemoryTokenRepositoryImpl" />

<!--  
<bean id="jdbcRememberMeTokenRepository"
	class="org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl">
	<property name="dataSource" ref="dataSource" />
</bean>
-->


被我注释掉的jdbcRememberMeTokenRepository需要一个数据库表
这个表用来持久化RememberMeToken,生产环境一般还是要这样做的。

create table persistent_logins (
username varchar(64) not null,
series varchar(64) primary key,
token varchar(64) not null,
last_used timestamp not null
);


anonymousProcessingFilter:如果用户不能通过验证则给添加一个匿名用户的角色

<bean id="anonymousProcessingFilter" class="org.springframework.security.web.authentication.AnonymousAuthenticationFilter">
	<property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS"/>
	<property name="key" value="#{securityKeys['anonymous']}"/>
</bean>


exceptionTranslationFilter: 验证通不过?没有访问权限?这个Filter决定如果出现异常了到底应该这么办。

<bean id="exceptionTranslationFilter" class="org.springframework.security.web.access.ExceptionTranslationFilter">
	<property name="authenticationEntryPoint" ref="authenticationEntryPoint" />
	<property name="accessDeniedHandler" ref="accessDeniedHandler" />
</bean>

<bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
	<property name="useForward" value="false" />
	<property name="loginFormUrl" value="/common/login" />
</bean>

<bean id="accessDeniedHandler" class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
	<property name="errorPage" value="/common/error"/>
</bean>


filterSecurityInterceptor:核心过滤器的最后一个。它完成最终的授权判断
下面的配置有点多,那是因为filterSecurityInterceptor是个懒家伙。
它把工作委托AuthenticationManager接口,AuthenticationManager接口也不真的干活,
它委托多个AuthenticationProvider接口,当然其中一个AuthenticationProvider还是要
把工作委托给我们的UserDetailsService实现的。最后投票决定到最终结果。

<bean id="filterSecurityInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
	<property name="authenticationManager" ref="customAuthenticationManager" />
	<property name="accessDecisionManager" ref="affirmativeBased" />
	<property name="securityMetadataSource">
		<security:filter-security-metadata-source use-expressions="true">
			<security:intercept-url pattern="/common/login" access="permitAll" />
			<security:intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')" />
		</security:filter-security-metadata-source>
	</property>
</bean>

<bean id="customAuthenticationManager" class="org.springframework.security.authentication.ProviderManager">
	<property name="authenticationEventPublisher" ref="defaultAuthEventPublisher"/>
	<property name="providers">
		<list>
			<ref local="daoAuthenticationProvider"/>
			<ref local="anonymousAuthenticationProvider"/>
			<ref local="rememberMeAuthenticationProvider"/>
		</list>
	</property>
</bean>

<!--
	这个Bean决定了投票策略,decisionVoters只要有任意一个决定通过,那么结果就是通过。
-->
<bean class="org.springframework.security.access.vote.AffirmativeBased" id="affirmativeBased">
	<property name="decisionVoters">
		<list>
			<ref bean="roleVoter"/>
			<ref bean="expressionVoter"/>
			<ref bean="authenticatedVoter"/>
		</list>
	</property>
</bean>

<bean class="org.springframework.security.access.vote.RoleVoter" id="roleVoter" />
<bean class="org.springframework.security.access.vote.AuthenticatedVoter" id="authenticatedVoter" />

<bean id="defaultAuthEventPublisher" class="org.springframework.security.authentication.DefaultAuthenticationEventPublisher"/>

<bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
	<property name="passwordEncoder" ref="md5PasswordEncoder"/>
	<property name="userDetailsService" ref="hibernateUserDetailsService" />
	<!-- 
	<property name="saltSource" ref="saltSource"/>
	-->
</bean>

<bean id="anonymousAuthenticationProvider" class="org.springframework.security.authentication.AnonymousAuthenticationProvider">
	<property name="key" value="#{securityKeys['anonymous']}" />
</bean>

<bean class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler" id="expressionHandler"/>

<bean class="org.springframework.security.web.access.expression.WebExpressionVoter" id="expressionVoter">
	<property name="expressionHandler" ref="expressionHandler"/>
</bean>


其他的工具bean

<util:properties id="securityKeys">
	<prop key="remember-me">182301IEKO1L73C181891TLTKABCNKA1956A7G9UPQXN</prop>
	<prop key="anonymous">BF93JFJ091N00Q7HF</prop>
</util:properties>

<alias name="userDao" alias="hibernateUserDetailsService"/>

<bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" id="md5PasswordEncoder" />

<!-- 如果使用 Sha加密的话,可以用一用 -->
<!--
<bean class="org.springframework.security.authentication.dao.ReflectionSaltSource" id="saltSource">
	<property name="userPropertyToUse" value="id"/>
</bean>
-->


How to
4.1 在MVC框架里,我该如何得到现在已经登录的用户?

User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();


4.2 登录页面我要做后端验证,我该怎么办?
usernamePasswordProcessingFilter实际完成这个功能,自己写这个类

public class ValidatedUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

 private Logger logger = LoggerFactory.getLogger(getClass());
 
 @Override
 public Authentication attemptAuthentication(HttpServletRequest request,
   HttpServletResponse response) throws AuthenticationException {

  Authentication result = null;
  Locale locale = request.getLocale();

  try {
   result = super.attemptAuthentication(request, response);
   logger.debug("登录成功");
  } catch (AuthenticationException failed) {
   logger.debug("登录失败");
   String msg = super.messages.getMessage("validator.login.fail", locale);
   request.setAttribute("fail", msg);
   throw failed;
  }
  return result;
 }
}
// 注意这个类是实现了MessageSourceAware接口的。你加载了国际化文件的MessageSource用就好了。但是一定要配置在 WebApplicationContext里,不要配置在springMVC特有的配置文件里。

来源:http://yingzhuo.iteye.com/blog/1478322

阅读更多
个人分类: java ee spring security
想对作者说点什么? 我来说一句

spring-security-3.1.0.RC3

2011年12月05日 15.25MB 下载

Spring Security 资料合集

2015年01月24日 4.94MB 下载

spring security demo

2017年11月12日 358KB 下载

springstarter

2009年12月18日 14.17MB 下载

spring security acl 实例

2017年12月01日 28.3MB 下载

spring security demo2

2017年11月30日 28.95MB 下载

spring security spring security

2009年10月12日 237KB 下载

没有更多推荐了,返回首页

不良信息举报

Spring security

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭