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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值