Spring Security 和 Spring Cache 填坑记录

山哥做的网站本来已经有登陆控制,用JDBC来存SessionId相关的信息。但是权限控制的设计在扩展性方面不太好。于是想试试高大上的 Spring Security ,听说Apache Shino也不错而且简单很多,但是Spring 的Actuator要用Spring Security,只好硬着头皮啃这件难啃的货了。用过之后,发现这货真的像网上人民吐槽的那样,相当的复杂麻烦!有好几次想放弃不用它了,但是凭着不服输的精神,还是钻研了下去。花了一个星期的工余时间,总算把原来的解决方案迁移到了Spring Security上。过程之中填坑无数,结果却是喜人的!于是纪录一下一些重要的point。

至于Spring Cache,上了Spring Security 之后,如果不上Cache,那性能我相信是惨不忍暏的!为什么?但凡登陆验证,你得和数据库里的username和password来compare吧?它的jdbc模式虽然自带Cache,但我不想填更多的坑,于是自己实现一个 UserDetailsService, 这样我的数据库结构自由很多,甚至以后不用关系型 数据库也可以。用了debug模式,发现访问每个页面它都会调用一次UserDetailsService,我不想测试它每次去读数据库的性能,人生苦短啊大叔!上缓存吧!!

Issue 1, 这个问题是 Spring Cache 贡献出来的。如果你也用同样的写法,可能你在开发环境没问题,但生产环境一定会遇到。

出错信息:java.lang.IllegalArgumentException: Null key returned for cache operation (maybe you are using named params on classes without debug info?)
根本原因:以下代码里,自定义了Key,于是Key generator不起效,它会用SpEL来把 name 这个参数作为Key。这是官网教程的例子,为什么会行不通呢?再反复斟酌官网教程,终于明白它说什么了,就是如果编译没选debug模式,编译出来的class文件是没有参数名的信息的,那么反射机制来获取这个参数的值,就找不到参数名字!只能用 #a0或者#p0来指代第一个参数,依此类推。(If for some reason the names are not available (e.g. no debug information), the argument names are also available under the #a<#arg> where #arg stands for the argument index (starting from 0).)
会出错的写法,引用 参数 name作为Cache的key。

@Cacheable(cachnames="user", key="#name")
public User findByName(String name);

正确的万能写法,这里把 user_ 作为 key的前缀,传参 name作为后缀。大功告成!亲个嘴儿!

@Cacheable(cachnames="user", key="'user_'.concat(#a0)")
public User findByName(String name);

参考网址:http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html#cache-spel-context

Issue 2,如果你的Entity类作为Cache, 用了JCache,那么一定要实现Serializable,里面所有的集合类,所有的自定义类,都要是Serializable。(比如ArrayList, LinkedList, ConcurrentSet等等都可以)

如果你做了Join Table,那么不能用LAZY的方式Join,否则从缓存解出来就会有问题。一定要用EAGER。
Join Table一定要注意,你的类返回不可以是Null纪录,否则从缓存解出来无法还原成一个真实的Entity,又会出错…

Issue 3, 用了Spring Security之后,页面ajax里的POST方法失效!Chrome环境下看,返回代码是 403 Forbidden。

出错原因: csrf 是默认启用的,如果是thymeleaf作为渲染器,在html form里面,spring security会自动加入一个hidden的 _csrf.token,可是 如果你上了 ajax,那臣妾就表示无能为力了。。
解决方案:请看如下两步骤,用来解决jquery的ajax request for POST:
Step 1, 在<head>里加如下function

<script type="text/javascript">
 function sendCsrfHeader (request) {
   var token = '[[${_csrf.token}]]';
   var header = '[[${_csrf.headerName}]]';
   request.setRequestHeader(header, token);
 }
</script>

Step 2, 在每个 POST的ajax里面,加上beforeSend:

$.ajax({
   type : "POST",
   url : [[@{/user/changePassword}]],
   dataType : 'json',
   async : true,
   data : { },
   beforeSend: function(request) {
     sendCsrfHeader(request);
   },
   success : function(responseText) {
  ...

Issue 4, Spring Security 的 authentication.isAuthenticated()不好使!明明没登陆,为什么这个为true呢?

出错原因: 虽然你没登陆,但是 anonymousUser 登陆了,这个时候你匿名能访问的页面里,也是 authenticated的!
解决方案:在thymeleaf里用spring4 dialect
1, Maven添加以下依赖:注意 3.0.2,RELEASE的 springsecurity4不支持3.0.2的thymeleaf,所以要指定version 为3.0.1.RELEASE.

<properties>
  <thymeleaf.version>3.0.2.RELEASE</thymeleaf.version>
  <thymeleaf-layout-dialect.version>2.1.1</thymeleaf-layout-dialect.version>
</properties>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
  <groupId>org.thymeleaf.extras</groupId>
  <artifactId>thymeleaf-extras-springsecurity4</artifactId>
  <version>3.0.1.RELEASE</version>
</dependency>

2, 在页面中用以下标签:
<div th:if="${#authentication.name == 'anonymousUser'}">请登陆</div>
这个办法太挫,而且现在Spring Security 它 hardcode了anonymousUser,以后指不定用什么名字,于是山哥写了一个工具类,拒绝hardcode!:

@Service
public class LoginUtil {
   //Use this to get the Anonymous User name. Because do not want to hardcode.
   private static AnonymousAuthenticationFilter anonymousAuthenticationFilter = new AnonymousAuthenticationFilter("internal_use");
   public boolean isLogin(Authentication aut) {
     if(aut == null || !aut.isAuthenticated()) {
       return false;
     } else
       return !aut.getName().equals(anonymousAuthenticationFilter.getPrincipal());
   }
}

待续……

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容