spring security 学习记录

最近在研究spring security。之前搭建了一个简单的spring security3项目,成功运行后,想试试spring security4.想来应该不用多麻烦,结果在准备不足的情况下,发现security4的框架搭建起来,也没问题,但是就是登陆不上。这个问题困扰了我3天,于是上网查资料,发现4和3还是有很多不一样的地方。在这里将使用security3以及升级到security4的时候遇到的问题记录下来。


1、call refresh...之类的问题

这个是很低级的问题。我出现这种问题有两个原因。一个是schema和xsd没有升级。由于配置是拷贝原来的3的,用正在4里schema自然要换成对应的版本。

还有一个原因是里面有中文注释。去掉中文注释就好。如果一定要加注释,英文好的人用英文表述,英文不要的就用品音吧。


2、MD5盐加密。

密码自然不可能明文存放。MD5+salt现在已经是很多人在用的情况了。但是spring security的UserDetail类不含salt这个属性,解决方法有几种:

1、使用username作为盐。这种方式其实不推荐,因为UserDetail自带的属性不多,而且相对固定的更少。

2、编写org.springframework.security.core.userdetails.User 的子类。我就是采取这种方式,多加了一个salt属性。这样可以在自己的UserDetailService实现类中将salt封装进去并返回。在配置文件中的saltSource中写salt这个属性完全没问题。


3、关于csrf

CSRF是用于防止跨域攻击的,在security3中是默认关闭的,但是在security4中默认开启。开启之后在登陆时候的form中要加一个input放csrf的token
<input type="text" name="${_csrf.parameterName}" id=""  value="${_csrf.token}"/>
这样表单提交之后才能正常验证。不加的话会一直返回403,显示Access Denied。
我这个对网站安全没研究的人就是这个原因被困了几天。


===============2017年2月16日更新============================
之前和朋友交流的时候他因为是用spring-boot搭建的环境,不知道怎么回事他的表单中没有这个值也可以,在request中会自动加入这个值。有人知道是怎么回事吗?
(2017年2月23日更:查了资料说如果用springMVC的form标签生成的form或者用thymeleaf模板生成的表单提交的时候会自动加上这个东西。所以不需要显式的写。springMVC的form标签没试过,朋友是用thymeleaf,所以他不用写。)

4、关于AuthenticationSuccessHandler

如果登陆成功后要记录当前登陆的时间,那么就要自己定义AuthenticationSuccessHandler了。使用过程中发现了几点问题。

1)default-target-url和always-use-default-target

如果使用了自定义的handler,在http的form-login节点中配置的default-target-url和always-use-default-target两个属性会失效。此时需要在自己定义的handler中去配置这两个属性。
<bean id="myAuthenticationSuccessHandler" class="com.yrsoft.security.MyAuthenticationSuccessHandler">
	   	<property name="defaultTargetUrl" value="/hello"></property>  
	    <property name="alwaysUseDefaultTargetUrl" value="true"></property>  
   </bean>

2)跳转到登陆前的页面

使用自定义的handler之后,因为我的handler继承自SimpleUrlAuthenticationSuccessHandler,这个功能就失效了。于是我去跟security的代码,发现security之前实现这个功能是使用的org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler。里面代码大概意思是会将上一次的request先缓存起来。具体的实现方法没有深入研究,不过既然security都已经实现了我们就不用自己写了。于是我用自定义的handler继承这个handler,代码如下:
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

	private Logger logger = LoggerFactory.getLogger(getClass());
	@Autowired
	private UserService userService;
	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws IOException, ServletException {
		SaltedUser saltedUser = (SaltedUser) authentication.getPrincipal();
		Long uid = saltedUser.getId();
		if(logger.isDebugEnabled()){
			logger.debug("Updating User lastLogin");
		}
		int res = userService.updateLoginDate(uid);
		if(res>0 && logger.isDebugEnabled()){
			logger.debug("Update Success : " + res);
		}
		logger.info(request.getRequestURI());
		super.onAuthenticationSuccess(request, response, authentication);
	}
}

就是先更新了数据库中需要的字段,然后再调用父类的同名方法。
等等。光是这样还是不够的。记得将alwaysUseDefaultTargetUrl设置为false。因为security3的源代码中的默认值就是false,所以不配置这个属性也可以。




===============2017年2月23日更新============================

5、关于http节点的auto-config和use-expressions

网上有些资料说这两个配置相互冲突。一个为true另外一个就要设置成false。这话不对。说明作者还没有理解这两个是做什么的。因为官网的例子中这两个值都是true的。
auto-config是让spring security帮我们做一些自动化的配置的。当然,我们自己手动配置的东西肯定优先级更高。
use-expressions是告诉spring security在检查权限的时候是不是使用SpEL的,SpEL是Spring的EL表达式。就是我们在Spring Security4中常见的类似hasRole(..)之类的东西。如果这个选项为true,则只能用SpEL去写权限的配置。如果为false,则SpEL表达式会失效。

另外,目测这个选项的true和false会影响默认的Voter。之前在SS3中发现使用的Voter是RoleVoter和AuthenticationVoter。在SS4中发现使用的默认Voter是WebExpressionVoter。
(这里就是ss3升级到ss4的坑之一。use-expressions在SS3中默认false而在SS4中默认true)
有兴趣的话看一下源码跟一下debug日志就知道了


===============2017年3月1日更新============================


配置从xml改为java config方式,此时的spring security为4.1.0.RELEASE,并且使用了thymeleaf

6、关于乱码问题

之前在xml的配置方式是加一个spring 的encoding filter。现在改成java config方式,按理说,在onStartUp方法中加一个FIlter即可。但是实际运行发现中文还是乱码。查阅资料发现同样有不少人遇到这样的问题但都没有效果。于是我就想是不是filter执行顺序的问题。我尝试在注册spring security filter的类中添加这个编码的Filter
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
import org.springframework.web.filter.CharacterEncodingFilter;

/**
 * 主要任务是注册springSecurityFilterChain Filter
 */
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

	protected boolean enableHttpSessionEventPublisher() {
		return true;
	}

	/**
	 * 添加编码过滤
	 */
	protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
		FilterRegistration.Dynamic characterEncodingFilter = servletContext.addFilter("encodingFilter",
				new CharacterEncodingFilter());
		characterEncodingFilter.setInitParameter("encoding", "UTF-8");
		characterEncodingFilter.setInitParameter("forceEncoding", "true");
		characterEncodingFilter.addMappingForUrlPatterns(null, false, "/*");
		super.beforeSpringSecurityFilterChain(servletContext);
	}
}

其中beforeSpringSecurityFilterChain 的意思是在Security的Filter chain执行前,在这里加了编码Filter之后果然解决问题。

7、关于AccessDecissionManager

之前是想尝试添加自己的Voter。
首先,spring security4默认使用的是org.springframework.security.web.access.expression.WebExpressionVoter
这个是我debug出来的。
于是我自己写voter的时候方式如下:
  /**
	     * 可在此方法中添加Voter
	     * @return
	     */
	    @Bean
	    public AccessDecisionManager accessDecisionManager(){
	    	List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<>();
	    	decisionVoters.add(roleVoter());
	    	decisionVoters.add(new AuthenticatedVoter());
	    	decisionVoters.add(new WebExpressionVoter());
	    	AbstractAccessDecisionManager accessDecisionManager = new AffirmativeBased(decisionVoters);
	    	accessDecisionManager.setMessageSource(messageSource);
	    	return accessDecisionManager;
	    }

添加之后登陆依然没问题,但是其他一个个问题接踵而来
首先是访问页面的时候报 Reqeust method 'GET' not supported.
然后是自己的 loginSuccessHandler不生效
再次是设置的 SessionManager不生效。
还有设置的登陆成功后的default target url也不生效

因为之前不确定是什么原因导致的,这个问题困扰了我3天!!3天啊~啥都没干。浪费时间了。
今天偶然的机会,发现去掉自定义的accessDecissionManager之后上述问题都不存在了!你知道我找你找的多辛苦吗亲!
我滴个亲娘啊..现在确定问题的根源就好办了。
查看了一下源码,源码里确实是使用了WebExpressionVoter没错。但是人家有自己的初始化方式。
org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer
@Override
	@SuppressWarnings("rawtypes")
	final List<AccessDecisionVoter<? extends Object>> getDecisionVoters(H http) {
		List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<AccessDecisionVoter<? extends Object>>();
		WebExpressionVoter expressionVoter = new WebExpressionVoter();
		expressionVoter.setExpressionHandler(getExpressionHandler(http));
		decisionVoters.add(expressionVoter);
		return decisionVoters;
	}
继续跟踪了一下getExpressionHandler方法,发现里面还做了大量的其他配置。而且访问修饰符都是private。要命的是这个getDecisionVoters的方法没有访问修饰符(default)。也就是说,如果我们要自己写voter,同时将默认的这个voter加上的话,会很麻烦。我目前是没打算再加voter了,所以用这个默认的就好,等需要自己加Voter的时候再说吧。

<2017年3月6日>
现在找到原因了,上述三个问题是因为添加验证码的时候加了一个Filter导致的。这个Filter继承了UsernamePasswordAuthenticationFilter。然后在attemptAuthentication方法最后调用了 super.attemptAuthentication(req,res)。导致调用完成之后不执行Spring Security的后续默认Filter。所以之前写的验证码功能有问题。
具体添加验证码功能的方法参考Phoenix-Smile的文章: Spring Security在登录验证中增加额外数据(如验证码)
另外,如果要添加自己的Voter的话,有一点要注意一下:之前说过,ss4原来默认添加了WebExpressionVoter,这个Voter的作用就是会支持如hasRole()、authenticated()等方便权限认证的方法,如果要调用这些方法的话(包括标签),记得加了自定义的Voter之后一定要添加这个Voter来支持这些方法,否则ss会解析不了表达式,在认证的时候报错。
</2017年3月6日>
<2017年3月10日>
实际运行发现上述链接中的添加验证码的方式有一个致命BUG:因为文章中的token,即输入的验证码是在CustomWebAuthenticationDetails的构造器中获取的。而在同一个session中这个Details只会初始化一次,这就导致了如果第一次输入错验证码,那么之后即使输入的是正确的,也会提示验证码不匹配,更严重的是,如果第一次输入正确,只要这次session没过期,之后输入任意验证码都算正确(包括登陆失败和登出的情况)。所以添加验证码不可以用provider的方式。
因为之前已经发现了问题所在,所以我参考源码之后还是写了一个Filter出来。详情见下方 添加验证码
</2017年3月10日>

8、关于修改'Role_' 的prefix

本来觉得这个没必要记。但是ss3和ss4的voter不一样。所以还是简单记一下
ss3的话,用accessDecisionManager,并且手动在xml里创建RoleVoter,设置RoleVoter的rolePrefix即可
<bean id="roleVoter"  class=" org.springframework.security.access.vote.RoleVoter">
		<property name="rolePrefix" value="unicorn_"></property>
	</bean>

ss4的话就简单了,我这使用的java config,看了一下源码里的注释,直接使用hasAuthority方法就行。如:
 http.authorizeRequests().antMatchers("/admin").hasAuthority("unicorn_ADMIN");
ss3中没有测试,但是如果有这个方法的话应该也可以。

===============2017年3月2日更新============================

9、在Thymeleaf中使用SpringSecurity标签

首先要说的是,如果要在Thymeleaf中使用Spring Security的标签,那么security的tag包可以不用添加,因为此包只能在jsp中使用,而thymeleaf是使用的html。

第一步要确保添加了thymeleaf-extras-springsecurity4.jar包
<dependency>
		    <groupId>org.thymeleaf.extras</groupId>
		    <artifactId>thymeleaf-extras-springsecurity4</artifactId>
		    <version>3.0.2.RELEASE</version>
		</dependency>

版本要和当前使用的spring security对应。如果是在使用spring security3,那添加的包就是thymeleaf-extras-springsecurity3.jar
这个dependency还会添加thymeleaf-layout-dialect.jar这个包。

然后在配置thymeleaf的引擎的时候记住添加dialect
private TemplateEngine templateEngine() {
		SpringTemplateEngine engine = new SpringTemplateEngine();
		engine.setTemplateResolver(templateResolver());
		engine.addDialect(new org.thymeleaf.extras.springsecurity4.dialect.SpringSecurityDialect());
		return engine;
	}


添加SpringSecurityDialect。
我之前的spring security标签不生效就是因为没有在这里添加这个东西,朋友给我的项目是Spring Boot搭建的,应该是因为使用了AutoConfig所以自动配置这个了。如果不用SpringBoot的话这里一定要加上。(附上搜索到的解决方案的帖子。 how to use secsomething in a thymeleaf expression

然后就是在html里声明了。
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"
      >

接下来就能在这个html里使用sec标签了。具体用法自行百度。

===============2017年3月9日更新============================

10、关于SpringBoot中静态资源的引用

项目修改成SpringBoot的方式启动,目录结构改成下图:


static目录中全是静态资源,而templates目录中全是html文件

templates目录的配置

SpringBoot 默认的html路径就是在classpath的templates目录下,注意是有s的。所以如果html在这个目录中的话MVC的resolver可以不用配prefix。

static目录的配置

今天查资料,不知道查到哪篇博客说静态资源默认是这个目录,害我引js和css没一个成功的。
最后发现两点要注意:
1、static不是默认的静态资源路径,需要配置。写路径的话需要写上static这个目录名。如我在thymeleaf中写的是@{static/css/bootstrap.min.css}
2、如果是放在static中,在配置resourceHandler的时候,要注意,在location中要加上classpath:
如:
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
		registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
		registry.addResourceHandler("/resource/**").addResourceLocations("classpath:/resource/");
		super.addResourceHandlers(registry);
	}
否则是引不到的。参考文章: SpringBoot静态资源处理
这里再次感谢isea533,此大大的博客已经帮我很多次了。

===============2017年3月10日更新============================

11、添加验证码

我添加验证码的过程可以说是一波三折。先是用Filter的方式,结果发现部分功能失效,当时找的问题原因方向找偏了,只能暂时去掉验证码的功能。等回头有时间看了,找到一个资料,按照资料上配置Provider的方式配置了之后,又发现了一个严重bug,并且同时获取不到当前在线的session。最后才回到更合理的Filter的方式,并且现在其他功能都能正常使用。前前后后花了我10天的时间。不过在此过程中对security更了解一些,也算是收货吧。
下面我就将我添加验证码的方式记录下来。

继承GenericFilterBean

首先,自定义的Filter继承了org.springframework.web.filter.GenericFilterBean这个类,这个类是Spring自定义的一个实现了Filter以及其他一系列注入类接口的抽象类。
为啥是继承这个类,不是实现Filter接口?恩...因为我懒。这个类自动实现了一些初始化的工作。(说白了就是不想重新写init方法和destory方法)
为啥不是UsernamePasswordAuthenticationFilter或者其父类AbstractAuthenticationProcessingFilter?因为AbstractAuthenticationProcessingFilter这个家伙的doFilter方法已经写好,一旦attemptAuthentication方法返回不为null认为认证成功,则不继续执行后续的Filter,若重写doFilter方法也不是不可以,只是这类中的attemptAuthentication方法是抽象方法,放一个空的在那里也不好看。(我写代码有洁癖……)

编写Filter时候的几点考虑

有几个核心的点要考虑到:
1、验证码Filter,只负责验证验证码是否匹配。而目前只有登录的时候才会填写验证码,所以这个Filter的拦截地址就是登录时候提交表单的地址,然后从里面获取到验证码进行匹配。
2、和之前一样,request中验证码的属性名称应该可配
3、因为这里抛出的异常信息是有可能被前端拿去展示的,所以为了支持国际化,要放一个MessageSource在里面来获取国际化信息。
4、添加AuthenticationFailureHandler。如果验证码匹配失败要按照security的方式处理失败。这个时候就需要一个AuthenticationFailureHandler。而成功的情况下是需要继续执行后续的Filter的。所以不需要AuthenticationSuccessHandler。

核心代码如下:
	public void doFilter(ServletRequest request,
			ServletResponse response,FilterChain chain) throws  IOException, ServletException,AuthenticationException {
		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse res = (HttpServletResponse) response;

		if (isLoginPage(req)) {	//判断是否是登陆的页面
			if (postOnly && !req.getMethod().equals("POST")) {	//判断是否只能POST请求
				throw new AuthenticationServiceException(
						"Authentication method not supported: " + req.getMethod());
			}
			
			String genCode = this.obtainGeneratedCaptcha(req);	//获取生成的验证码
			String inputCode = this.obtainCaptcha(req);		//获取输入的验证码
			if (genCode == null) {	//验证失败的情况下通过failureHandler处理请求
				failureHandler.onAuthenticationFailure(req, res,
						new CaptchaException(this.messages.getMessage("LoginAuthentication.captchaInvalid")));
				return;
			}
			if (!genCode.equalsIgnoreCase(inputCode)) {
				failureHandler.onAuthenticationFailure(req, res,
						new CaptchaException(this.messages.getMessage("LoginAuthentication.captchaNotEquals")));
				return;
			}
		}
		//验证成功的情况下继续后续的Filter
		chain.doFilter(request, response);

	}

这样去配置一下即可。我是用javaconfig的 http.addFilterAt 的方式替换UsernamePasswordAuthenticationFilter的,已经测试运行成功。
如果用springMVC的Filter添加方式应该也可以,不过会比较麻烦,因为有一些属性是一定要定义而在security中是定义好可以直接拿来用的,比如FailureHandler。这种方式我也还没测试。

===============2017年3月13日更新============================

12、添加druid监控配置

按照配置文档添加druid监控配置,在properties文件中编辑完对应的属性后,在Java Config中有三个bean是一定要写的,如下:

	@Bean
	@ConfigurationProperties(prefix="spring.datasource")
	public DataSource druidDataSource(){
		return new DruidDataSource();
	}
	/*以下两个bean配置druid监控功能*/
	@Bean
	public ServletRegistrationBean druidServlet() {
		ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
		//是否能够重置数据.
		servletRegistrationBean.addInitParameter("resetEnable","false");
		return servletRegistrationBean;
	}
	@Bean
	public FilterRegistrationBean filterRegistrationBean() {
		FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
		filterRegistrationBean.setFilter(new WebStatFilter());
		filterRegistrationBean.addUrlPatterns("/*");
		filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
		return filterRegistrationBean;
	}

一般的情况下这样配置就够了。但是在我的项目里,这样配置不生效。情况是:druid的监控界面可以出来,但是页面中没有任何数据。
刚开始还以为自己配置有问题,上网查了一下资料,都是这样配置的。还有些资料说版本问题,1.0.12可能没数据,换成1.0.11就可以了。尽管我的是1.0.18,我还是换成了1.0.11试试,结果还是不行。鬼使神差下打开页面debug了一下,发现请求了一个basic.json,这个json没有请求成功,response返回到了没有权限的页面。再看后台日志,是卡在了CsrfFilter上。于是再返回去看,果然这个json是用POST方式请求的。
问题到这里就很明朗了,原因是我加了CSRF防御,用POST方式请求的情况下,请求中一定要加一个CSRF的token。之前的有说过,在form表单用post方式提交的情况下,用thymeleaf或者mvc生成的表单,或者手动在表单里提交一个csrf的token,都可以通过post请求。但是这个post是druid自己发出的,怎么办呢?
我的处理办法是想办法将这个json地址过滤掉,让他跳过CsrfFilter。于是我又开始了查看源码。

查看源码当然是从http这里开始,要先了解原作者是怎么设置的这些东西。我发现http.csrf()后面可以点出来一个叫requireCsrfProtectionMatcher()的方法,这个貌似是个设置matcher的方法,嫌疑最大。点开源码,发现这个方法果然设置了一个matcher的变量。
	public CsrfConfigurer<H> requireCsrfProtectionMatcher(
			RequestMatcher requireCsrfProtectionMatcher) {
		Assert.notNull(requireCsrfProtectionMatcher,
				"requireCsrfProtectionMatcher cannot be null");
		this.requireCsrfProtectionMatcher = requireCsrfProtectionMatcher;
		return this;
	}

继续看这个变量,发现有一个getter,但是是private的,看这个getter,发现跑到config方法里去了。而且,这个config的第一行就是new了一个CsrfFilter,第二行就是将这个matcher配置到这个新鲜出炉的CsrfFilter中。所以,接下来的工作就是写一个matcher并通过requireCsrfProtectionMatcher()方法将其设置进去就行了。

通过看源码发现,CsrfFilter默认的matcher是一个在CsrfFilter的静态内部类,叫DefaultRequiresCsrfMatcher。但是人家是private final的。观察一下filter中调用这个matcher的方法,是通过RequestMatcher接口的matches方法判断是否需要过滤。如果这个方法返回false,那就不过滤,直接调用FilterChain中的下一个Filter。

于是自己写了一个CsrfSecurityRequestMatcher,这个matcher需要 implements RequestMatcher。然后在csrf()中设置进去,再次测试,果然现在有数据了。

===============2017年3月14日更新============================
今天发现,原来SpringSecurity给我们留着解决这个问题的办法。在配置的时候按照如下代码配置即可:
http.csrf().ignoringAntMatchers("/druid/**")
这个是配置忽略csrf验证的列表。参数类型是一个String类型的可变长参数。
需要注意的是,这里不能配置 /druid** ,一定要/druid/** 这样配置才生效。因为他的RequestMatcher和我的requestMatcher实现方式不一样。

个人还是推荐这种,不推荐自己实现。已经有可以直接用的轮子了,直接拿来组装就好,没必要再自己造轮子。



===============2017年3月15日更新============================

13、前端获取CsrfToken

Spring Srcurity开启csrf功能之后,所有post请求都要加上csrfToken,而且只要session不过期,这个token就不会变。在我们的应用中,除了表单之外,有些情况下我们需要用ajax去获取数据,而为了数据安全,又不得不使用post方法。这个时候如果请求的路径不在排除列表中,那就必须加上csrfToken了,但是这个token哪里来?其实,Spring Security已经帮我们传到前端了。这个Token是在request的attribute中,attribute名称为 _csrf。注意名称前有个下划线。只是这样还没完,这个获取到的是org.springframework.security.web.csrf.DefaultCsrfToken对象,我们要的是里面的token这个值。所以,如果是thymeleaf要获取的话,那就是
<input type="hidden" id="csrfToken" th:value="${#httpServletRequest.getAttribute('_csrf').token}" />
写了这个之后,就可以在js中通过读取这个id的value来获取csrf的Token了。
当然,如果是打算用form提交数据的话就不用这么麻烦了,酱form的action用th标签生成即可,thymeleaf会在form中自动给我们加上这行。


===============2017年9月25日更新============================

14、使用外部容器运行spring boot 项目

使用spring boot开发确实方便,里面有自带的tomcat,可以方便的调式和运行。但是因为是为服务的形式,不方便项目管理。所以如果项目稍大,就不适合使用自带的tomcat跑了。这个时候就需要使用外部的容器去运行项目。项目配置需要做如下改造:

1、修改pom文件

首先,pom文件的打包形式要从jar变成war。这个不用多说。
其次,要去除自带的tomcat组件。如下:
	<dependency>  
            <groupId>org.springframework.boot</groupId>  
            <artifactId>spring-boot-starter-web</artifactId>  
            <exclusions>  
                <exclusion>  
                    <groupId>org.springframework.boot</groupId>  
                    <artifactId>spring-boot-starter-tomcat</artifactId>  
                </exclusion>  
            </exclusions>  

2、修改代码

让你的主函数所在类继承org.springframework.boot.web.support.SpringBootServletInitializer。
示例代码如下:
@SpringBootApplication
public class UnicornSecurityApplication extends SpringBootServletInitializer{
	/** 
     * 实现SpringBootServletInitializer可以让spring-boot项目在web容器中运行 
     */  
    @Override  
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {  
        builder.sources(this.getClass());  
        return super.configure(builder);  
    }  
	public static void main(String[] args) throws Exception{
		SpringApplication.run(UnicornSecurityApplication.class, args);
	}
}

最后,别忘了添加WEB-INF目录以及web.xml。如果使用的Web Module为3.0+,那么可以省略此步骤。因为3.0+可以不用web.xml
此时你的项目已经可以在外部容器下运行了。

15、关于使用spring session redis在第三方容器下的失效问题

首先描述一下问题。我的spring boot security 集成了spring session来管理session,并使用了redis来保存session。使用spring boot的微服务方式启动一切正常。但是当我切换成使用外部的tomcat容器启动项目用时,发现session并没有存储到redis中。起初我以为是我的配置有问题。我创建了另外一个security+spring session的项目,使用内部的tomcat运行,没有问题。此时我对比了两边的所有配置,发现并没有什么不同,但是我注意到,在我查阅spring security集成spring session的官方文档( https://docs.spring.io/spring-session/docs/current/reference/html5/guides/security.html)时,有如下说明:
The @EnableRedisHttpSession annotation creates a Spring Bean with the name of springSessionRepositoryFilter that implements Filter. The filter is what is in charge of replacing the HttpSession implementation to be backed by Spring Session. In this instance Spring Session is backed by Redis.

Last we need to ensure that our Servlet Container (i.e. Tomcat) uses our springSessionRepositoryFilter for every request. It is extremely important that Spring Session’s springSessionRepositoryFilter is invoked before Spring Security’s springSecurityFilterChain. This ensures that the HttpSession that Spring Security uses is backed by Spring Session. Fortunately, Spring Session provides a utility class named
有一个名字叫springSessionRepositoryFilter的filter并且要保证他生效才能使用spring session?
同样根据这篇文章,@EnableRedisHttpSession 注解会自动添加这个filter。但是实际情况表明,在使用外部容器的情况下,这个filter很可能没有被激活。所以我查阅了源码,并在继承了org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer的配置类中,手动添加了这个filter:
protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
		FilterRegistration.Dynamic springSessionRepositoryFilter = servletContext.addFilter("springSessionRepositoryFilter",
				new DelegatingFilterProxy());
		springSessionRepositoryFilter.addMappingForUrlPatterns(null, false, "/*");
		super.beforeSpringSecurityFilterChain(servletContext);
	}

问题解决。

另外需要注意的是,在mvc的配置中添加这个filter是没有用的,一定要在上面提到的位置去添加。还有,这个名字我不知道在spring中有没有什么特殊的意义,就算没有,我也懒得改了。

其他发现再更新...

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值