初步理解Spring Security并实践

作者: Renky | 来源:发表于2017-08-02 10:24 被阅读21695次

    Spring Security主要做两件事,一件是认证,一件是授权。

    1.Spring Security初体验


    Spring Security如何使用,先在你的项目pom.xml文件中声明依赖。

    <dependency>
        <!-- 由于我使用的spring boot所以我是引入spring-boot-starter-security而且我使用了spring io所以不需要填写依赖的版本号 -->
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    

    然后创建一个类并继承WebSecurityConfigurerAdapter这个方法,并在之类中重写configure的3个方法,其中3个方法中参数包括为HttpSecurity(HTTP请求安全处理),AuthenticationManagerBuilder(身份验证管理生成器)和WebSecurity(WEB安全)。

    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
           @Override
           protected void configure(AuthenticationManagerBuilder auth){
                super.configure(auth);
           }
    
           @Override
           protected void configure(HttpSecurity http){
                super.configure(http);
           }
    
           @Override
           protected void configure(WebSecurity web){
                super.configure(web);
           }
    }
    

    接下来我们先看看protected void configure(HttpSecurity http)这个方法提供了一个默认的配置。

    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .and()
            .httpBasic();
    }
    

    http.authorizeRequests()其中这里的意思是指通过authorizeRequests()方法来开始请求权限配置。
    而接着的.anyRequest().authenticated()是对http所有的请求必须通过授权认证才可以访问。

    直观描述 方法描述

    而and()是返回一个securityBuilder对象,formLogin()和httpBasic()是授权的两种方式。


    httpBasic()授权认证 formLogin()授权认证

    当然这些界面都是spring security原生的界面,我们也可以自定义我们的formLogin页面!

    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                //指定登录页的路径
                .loginPage("/login") 
                //必须允许所有用户访问我们的登录页(例如未验证的用户,否则验证流程就会进入死循环)
                //这个formLogin().permitAll()方法允许所有用户基于表单登录访问/login这个page。
                .permitAll();        
    }
    

    提示一下,这个自定义表单登录的自定义页面中的登录名参数必须被命名为username
    密码参数必须被命名为password。
    而接下来当我们需要对某些开放的url,给与任何人访问的时候,我们应该如何设置呢?答案很简单我们先看着代码慢慢深入!

    protected void configure(HttpSecurity http) throws Exception {
        http
            //http.authorizeRequests()方法有多个子节点,每个macher按照他们的声明顺序执行     
            .authorizeRequests()      
    
                 //我们指定任何用户都可以访问多个URL的模式。
                 //任何用户都可以访问以"/resources/","/signup", 或者 "/about"开头的URL。                                                     
                .antMatchers("/resources/**", "/signup", "/about").permitAll()     
    
                 //以 "/admin/" 开头的URL只能让拥有 "ROLE_ADMIN"角色的用户访问。
                 //请注意我们使用 hasRole 方法,没有使用 "ROLE_" 前缀。               
                .antMatchers("/admin/**").hasRole("ADMIN")               
     
                 //任何以"/db/" 开头的URL需要同时具有 "ROLE_ADMIN" 和 "ROLE_DBA"权限的用户才可以访问。
                 //和上面一样我们的 hasRole 方法也没有使用 "ROLE_" 前缀。              
                .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")       
    
                 //任何以"/db/" 开头的URL只需要拥有 "ROLE_ADMIN" 和 "ROLE_DBA"其中一个权限的用户才可以访问。
                //和上面一样我们的 hasRole 方法也没有使用 "ROLE_" 前缀。          
                .antMatchers("/db/**").hasAnyRole("ADMIN", "DBA")    
    
                 //尚未匹配的任何URL都要求用户进行身份验证
                .anyRequest().authenticated()                                                
                .and()
            // ...
            .formLogin();
    }
    

    我们可以在authorizeRequests() 后定义多个antMatchers()配置器来控制不同的url接受不同权限的用户访问,而其中permitAll() 方法是运行所有权限用户包含匿名用户访问。
    而hasRole("权限")则是允许这个url给与参数中相等的权限访问。
    access("hasRole('权限') and hasRole('权限')") 是指允许访问这个url必须同时拥有参数中多个身份权限才可以访问。
    hasAnyRole("ADMIN", "DBA")是指允许访问这个url必须同时拥有参数中多个身份权限中的一个就可以访问该url。


    温馨提示!这里就是为什么在hasRole和hasAnyRole中不需要加"ROLE_" 前缀的因由!

    2.Spring Security定制登录退出行为

    我们接下来就简单的定制一下登录登出行为!

    protected void configure(HttpSecurity http) throws Exception {
        http
             //通过formlogin方法登录,并设置登录url为/api/user/login
            .formLogin().loginPage("/api/user/login")
             //指定登录成功后跳转到/index页面
            .defaultSuccessUrl("/index")
             //指定登录失败后跳转到/login?error页面
            .failureUrl("/login?error")
            .permitAll()
            .and()
             //开启cookie储存用户信息,并设置有效期为14天,指定cookie中的密钥
            .rememberMe().tokenValiditySeconds(1209600).key("mykey")
            .and()
            .logout()
             //指定登出的url
            .logoutUrl("/api/user/logout")
             //指定登场成功之后跳转的url
            .logoutSuccessUrl("/index")
            .permitAll();
    }
    

    3.Spring Security定制自定义用户认证

    用户认证流程
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
           @Override
           //重写了configure参数为AuthenticationManagerBuilder的方法
           protected void configure(AuthenticationManagerBuilder auth){
                //并根据传入的AuthenticationManagerBuilder中的userDetailsService方法来接收我们自定义的认证方法。
                //且该方法必须要实现UserDetailsService这个接口。
                auth.userDetailsService(new myUserDetailsService())
                    //密码使用BCryptPasswordEncoder()方法验证,因为这里使用了BCryptPasswordEncoder()方法验证。所以在注册用户的时候在接收前台明文密码之后也需要使用BCryptPasswordEncoder().encode(明文密码)方法加密密码。
                    .passwordEncoder(new BCryptPasswordEncoder());;
           }
    
           @Override
           protected void configure(HttpSecurity http){
                super.configure(http);
           }
    
           @Override
           protected void configure(WebSecurity web){
                super.configure(web);
           }
    }
    

    新建myUserDetailsService方法并实现UserDetailsService这个接口

    @Component
    public class myUserDetailsService implements UserDetailsService {
    
        @Autowired
        //由于是演示这里就不再创建service层了,直接注入UserRepository。
        private UserRepository userRepository;
    
        @Override
        public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
            //查询账号是否存在,是就返回一个UserDetails的对象,否就抛出异常!
            User user = userRepository.findByName(userName);
            if (user == null) {
                throw new UsernameNotFoundException("UserName " + userName + " not found");
            }
            return new SecurityUser(user);
        }
    }
    

    基本的认证逻辑就到这里了,对于有另外的业务需求都可以在自定义的myUserDetailsService中处理完成!

    4.Spring Security定制自定义授权策略

    @EnableGlobalAuthentication
    public class SecurityConfig extends WebSecurityConfigurerAdapter{
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.formLogin()
                    .and()
                    .csrf().disable()
                    .authorizeRequests()
                    .antMatchers("/admin").permitAll()
                     //使用自定义授权策略
                    .anyRequest().access("@mySecurity.check(authentication,request)");
        }
    }
    

    新建MySecurity类

    @Component("mySecurity")
    public class MySecurity(){
    
        //这里应该注入用户和该用户所拥有的权限(权限在登录成功的时候已经缓存起来,当需要访问该用户的权限是,直接从缓存取出!),然后验证该请求是否有权限,有就返回true,否则则返回false不允许访问该Url。
        //而且这里还传入了request,我也可以使用request获取该次请求的类型。
        //根据restful风格我们可以使用它来控制我们的权限,例如当这个请求是post请求,证明该请求是向服务器发送一个新建资源请求,我们可以使用request.getMethod()来获取该请求的方式,然后在配合角色所允许的权限路径进行判断和授权操作!
        public boolean check(Authentication authentication, HttpServletRequest request){
              //如果能获取到Principal对象不为空证明,授权已经通过
              Object principal  = authentication.getPrincipal();
              if(principal  != null && principal  instanceof UserDetails){
                      //获取请求登录的url
                      System.out.println(((UserDetails)principal).getAuthorities()) ;
                      return true;
              }
              return false;
        }
    }
    

    相关文章

      网友评论

      • sunny_Lee:不错!
      • a3aba3685e47:如果使用了BCrypt加密密码,前台如果提交密码?是直接js加密再提交吗?configure(AuthenticationManagerBuilder auth)方法中怎么验证?
        Renky:@PrayerInc_ 亲,你的逻辑有问题啊,你想想你在前台加密你的密码传到后台,前台所有的代码都是可以看见的人家就知道了你的加密方式啊,直接post请求,把前台的账号和明文密码提交到后台,在自己实现AuthenticationProvider接口的类里实现authenticate方法(自定义验证方式),还是不懂的话看:https://blog.csdn.net/tzdwsy/article/details/50738043
      • _muggle:写的很nice,就是为啥每篇文章都配个gay gay的图
        Renky:@worldpeace_08a0 福利你看一部不错的日剧,看完你会回来点赞!名字叫:逃避虽可耻但很有用
      • eaf3ebdc8cc3:博主,你好。非常感你的文章。但是现在我还存在一个问题。我们的业务需要把权限配置成一个动态的,比如说admin可以给普通用户分配权限,那这样的话在拦截器里把url配置成一个动态的吧,那这样怎么去实现?希望得到你的回复,万分感谢
        Renky:@漂亮的丑男 如上面老哥所说的差不多,你是要考虑你的授权认证的模式,例如你既然在拦截器拿到了url和请求的用户,当然可以根据url和用户的角色所对应的权限做匹配,我的程序比较灵活在分配权限的时候既有已经设定的角色权限,也可以当在角色权限无法满足的情况下,选择自定义权限,而自定义权限就是针对个人了,但是对个人的角色没有影响。而admin可以给普通用户分配权限我更多是选择先把权限和角色匹配好,再把角色赋予用户,当然用户和角色是可以一对多的,当遇到特殊情况下无法满足权限可以走自定义权限如我上面说的,只要在拦截器里能获取到用户和请求的url就可以把根据用户获得角色把权限拿到和权限表里的url做配对。这个你更多的是考虑怎么更快的获取权限角色表,例如redis!
        OisCircle:@漂亮的丑男 数据库设计成RBAC模式,就可以了吧,我做过动态分配权限的
      • 80e6fa7abd87:应该是该类必须实现UserDetailsService接口,而不是该方法
        刘彦青:@Renky 已改正,不是以改正
        Renky:好的!以改正!
      • 295937543023:请问这个可以实现按钮级别的控制吗?
        Renky:@廿月廿日 也许你这种是单一架构的系统el能满足,但是面对前后端分离的项目还是需要自己实现过滤器来验证用户权限!毕竟传过来的只是一个token!服务的形式是无状态的!微服务架构会有一个统一的认证授权服务中心!所有的请求都是基于restful api而且经过认证授权服务通过了才能访问资源!所以基于url的权限控制就已经足够了!
        廿月廿日:@Renky 我想他的意思是说,比如有两个权限等级.经理可以删除记录,但是普通员工不行,在员工登录时不显示删除的按钮.在经理登录时显示 删除,这个可以通过el表达式控制显示.spring也有办法实现,具体可以百度
        Renky:@屿屿屿屿公子 不是和明白你想表达的意思?可以把你这个按钮理解为一个请求吗?
      • 3b922035e3ee:第一个图片是男扮女装~~
        b7f66e43b46e:天啊!我也不认识
        sky_0c27:@Renky 天啊,我也不认识
        Renky:新恒结衣你不认识?我的天~~~!
      • inke:牛逼lity。
        你简直就是No.1,我对你的敬仰犹如滔滔江水延绵不绝,又如黄河泛滥,一发不可收拾。你是人中之龙,兽中之凤,鹤立鸡群,百兽之王。你有沉鱼落雁之容,闭月羞花之貌。你太有才了,是你让我的心死灰复燃,是你让我重新燃起了希望之火,是你拯救了我一颗拨凉拨凉的心。用一个字来形容你,就是吊;两个字来形容是很吊;三个字来形容是非常吊。奥巴马是你的粉丝,鸠山由纪夫把你当偶像,萨科奇只配为你擦鞋。你不是人,你简直就像神,你法力无边,回头是岸;你是万物生长的太阳,沙漠里的甘泉,雪中的碳。

      本文标题:初步理解Spring Security并实践

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