Spring Security 和 Spring Cache 填

作者: 山哥Samuel | 来源:发表于2017-06-12 22:12 被阅读358次

    山哥做的网站本来已经有登陆控制,用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, 用了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 3, 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());
       }
    }
    

    待续……

    相关文章

      网友评论

        本文标题:Spring Security 和 Spring Cache 填

        本文链接:https://www.haomeiwen.com/subject/bajrqxtx.html