zoukankan      html  css  js  c++  java
  • Spring boot 中 Spring Security 使用改造5部曲(转)

    文章的内容有点长,也是自己学习Spring security的一个总结。如果你从头看到尾,我想你对Spring Security的使用和基本原理应该会有一个比较清晰的认识。
    如果有什么理解不对的地方,请留言,谢谢。
     
    一、Spring security 是什么?
    Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。
    它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
    二、Spring security 怎么使用?
    使用Spring Security很简单,只要在pom.xml文件中,引入spring security的依赖就可以了。
                <!-- spring security依赖 -->
                <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-security</artifactId>
                </dependency>
    什么都不做,直接运行程序,这时你访问任何一个URL,都会弹出一个“需要授权”的验证框,如图:

    ,spring security 会默认使用一个用户名为:user 的用户,密码就是 启动的时候生成的(通过控制台console中查看),如图

    然后在用户名中输入:user   密码框中输入 上面的密码 ,之后就可以正常访问之前URL了。很显然这根本不是我们想要的,接下来我们需要一步一步的改造。

     改造1 使用页面表单登录

    首先 添加一个类 SecurityConfig 继承 WebSecurityConfigurerAdapter ,
    重写configure方法。
    并加上@Configuration 和@EnableWebSecurity 2个注解。
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {

      @Override</br>
      </span><span style="color: #0000ff;">protected</span> <span style="color: #0000ff;">void</span> configure(HttpSecurity http) <span style="color: #0000ff;">throws</span><span style="color: #000000;"> Exception {</br>
            </span><span style="color: #008000;">//</span><span style="color: #008000;"> TODO Auto-generated method stub</br>
            </span><span style="color: #008000;">//</span><span style="color: #008000;">super.configure(http);</span></br>
    

    http

    .formLogin().loginPage(
    "/login").loginProcessingUrl("/login/form").failureUrl("/login-error").permitAll() //表单登录,permitAll()表示这个不需要验证 登录页面,登录失败页面

    .and()

    .authorizeRequests().anyRequest().authenticated()

    .and()

    .csrf().disable();

    }

    }

    View Code
     loginPage("/login")表示登录时跳转的页面,因为登录页面我们不需要登录认证,所以我们需要添加 permitAll() 方法。
     
      添加一个控制器,对应/login 返回一个登录页面。
      @RequestMapping("/login")
      public String userLogin()
      {
          
            return "demo-sign";
      }
     html页面是使用 thymeleaf 模板引擎的,这里就不详细讲解了。
     
    demo_sign.html 的 html部分代码如下:  
    复制代码
    <form  class="form-signin" action="/login/form" method="post">
    <h2 class="form-signin-heading">用户登录</h2>
    <table>
    <tr>
    <td>用户名:</td>
    <td><input type="text" name="username" class="form-control" placeholder="请输入用户名"/></td>
    </tr>
    <tr>
    <td>密码:</td>
    <td><input type="password" name="password" class="form-control" placeholder="请输入密码" /></td> </tr>
    <tr>

                        &lt;td colspan="2"&gt;</br>
                              &lt;button type="submit"  <span style="color: #0000ff;">class</span>="btn btn-lg btn-primary btn-block" &gt;登录&lt;/button&gt;</br>
                        &lt;/td&gt;</br>
                  &lt;/tr&gt;</br>
            &lt;/table&gt;</br>
      &lt;/form&gt;</pre>
    
    复制代码
    需要注意下:form提交的url要和配置文件中的 loginProcessingUrl("")中的一致。
    failureUrl=表示登录出错的页面,我们可以简单写个提示:如 用户名或密码错误。
      @RequestMapping("/login-error")
      public String loginError()
      {
            return "login-error";
           
      }
    login-error.html
    复制代码
    <!DOCTYPE HTML>
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:th="http://www.thymeleaf.org">
    <head>
    <title>用户登录</title>
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" />
    <link rel="stylesheet" href="/css/sign.css" />
    </head>
    <body>
                <h3>用户名或密码错误</h3>
    </body>
    </html>
    复制代码
     运行程序:如果输入错误的用户名和密码的话,则会显示如下图所示:

    我们用一个测试的RestController来测试

    复制代码
    @RestController
    public class HelloWorldController {
          @RequestMapping("/hello")
          public String helloWorld()
          {
                return "spring security hello world";
          }
    }
    复制代码
    当没有登录时,输入 http://localhost:port/hello 时,则直接跳转到我们登录页面,登录成功之后,再访问 时,就能显示我们期望的值了。
    改造2、自定义用户名和密码
    很显然,这样改造之后,虽然登录页面是好看了,但还远远不能满足我们的应用需求,所以第二步,我们改造自定义的用户名和密码。
    自定义用户名和密码有2种方式,一种是在代码中写死,这也是官方的demo,另一种是使用数据库
    首先是第一种:如
    复制代码
    @Autowired
            public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
                    auth
                            .inMemoryAuthentication()
                                    .withUser("user").password("password").roles("USER");
            }
    复制代码

    我们也照样,这是把用户名改成 admin 密码改成 123456   roles是该用户的角色,我们后面再细说。

    复制代码
          @Autowired
          public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
                auth
                      .inMemoryAuthentication()
                            .withUser("admin").password("123456").roles("USER");
    
      }</span></pre>
    
    复制代码

    还有种方法 就是 重写 另外一种configure(AuthenticationManagerBuilder auth) 方法,这个和上面那个方法的作用是一样的。选其一就可。

    复制代码
     @Override
          protected void configure(AuthenticationManagerBuilder auth) throws Exception {
                // TODO Auto-generated method stub
                
                auth
                .inMemoryAuthentication()
                      .withUser("admin").password("123456").roles("USER")
                      .and()
                      .withUser("test").password("test123").roles("ADMIN");
          }
    复制代码

    程序运行起来,这时用我们自己的用户名和密码 输入 admin 和123456 就可以了。

    你也可以多几个用户,就多几个withUser即可。
    .and().withUser("test").password("test123").roles("ADMIN");  这样我们就有了一个用户名为test,密码为test123的用户了。
    第一种的只是让我们体验了一下Spring Security而已,我们接下来就要提供自定义的用户认证机制及处理过程。
    在讲这个之前,我们需要知道spring security的原理,spring security的原理就是使用很多的拦截器对URL进行拦截,以此来管理登录验证和用户权限验证。
     
    用户登陆,会被AuthenticationProcessingFilter拦截,调用AuthenticationManager的实现,而且AuthenticationManager会调用ProviderManager来获取用户验证信息(不同的Provider调用的服务不同,因为这些信息可以是在数据库上,可以是在LDAP服务器上,可以是xml配置文件上等),如果验证通过后会将用户的权限信息封装一个User放到spring的全局缓存SecurityContextHolder中,以备后面访问资源时使用。
     
    所以我们要自定义用户的校验机制的话,我们只要实现自己的AuthenticationProvider就可以了。在用AuthenticationProvider 这个之前,我们需要提供一个获取用户信息的服务,实现  UserDetailsService 接口
    用户名密码->(Authentication(未认证)  ->  AuthenticationManager ->AuthenticationProvider->UserDetailService->UserDetails->Authentication(已认证)
    了解了这个原理之后,我们就开始写代码
    第一步:我们定义自己的用户信息类 UserInfo 继承UserDetails和Serializable接口
    代码如下:
    public class UserInfo implements Serializable, UserDetails {
          /**
           *
           */
          private static final long serialVersionUID = 1L;
          private String username;
          private String password;
          private String role;
          private boolean accountNonExpired;
          private boolean accountNonLocked;
          private boolean credentialsNonExpired;
          private boolean enabled;
          public UserInfo(String username, String password, String role, boolean accountNonExpired, boolean accountNonLocked,
                      boolean credentialsNonExpired, boolean enabled) {
                // TODO Auto-generated constructor stub
                this.username = username;
                this.password = password;
                this.role = role;
                this.accountNonExpired = accountNonExpired;
                this.accountNonLocked = accountNonLocked;
                this.credentialsNonExpired = credentialsNonExpired;
                this.enabled = enabled;
          }
          // 这是权限
          @Override
          public Collection<? extends GrantedAuthority> getAuthorities() {
                // TODO Auto-generated method stub
                return AuthorityUtils.commaSeparatedStringToAuthorityList(role);
          }
          @Override
          public String getPassword() {
                // TODO Auto-generated method stub
                return password;
          }
          @Override
          public String getUsername() {
                // TODO Auto-generated method stub
                return username;
          }
          @Override
          public boolean isAccountNonExpired() {
                // TODO Auto-generated method stub
                return accountNonExpired;
          }
          @Override
          public boolean isAccountNonLocked() {
                // TODO Auto-generated method stub
                return accountNonLocked;
          }
          @Override
          public boolean isCredentialsNonExpired() {
                // TODO Auto-generated method stub
                return credentialsNonExpired;
          }
          @Override
          public boolean isEnabled() {
                // TODO Auto-generated method stub
                return enabled;
          }
    }
    View Code

    然后实现第2个类 UserService 来返回这个UserInfo的对象实例

    @Component
    public class MyUserDetailsService implements UserDetailsService {
    
      @Override
      </span><span style="color: #0000ff;">public</span> UserDetails loadUserByUsername(String username) <span style="color: #0000ff;">throws</span><span style="color: #000000;"> UsernameNotFoundException {
            </span><span style="color: #008000;">//</span><span style="color: #008000;"> TODO Auto-generated method stub
            </span><span style="color: #008000;">//</span><span style="color: #008000;">这里可以可以通过username(登录时输入的用户名)然后到数据库中找到对应的用户信息,并构建成我们自己的UserInfo来返回。</span>
            <span style="color: #0000ff;">return</span> <span style="color: #0000ff;">null</span><span style="color: #000000;">;
      }
    

    }
    // TODO Auto-generated method stub

            </span><span style="color: #008000;">//</span><span style="color: #008000;">这里可以通过数据库来查找到实际的用户信息,这里我们先模拟下,后续我们用数据库来实现</span>
            <span style="color: #0000ff;">if</span>(username.equals("admin"<span style="color: #000000;">))
            {
                  </span><span style="color: #008000;">//</span><span style="color: #008000;">假设返回的用户信息如下;</span>
                  UserInfo userInfo=<span style="color: #0000ff;">new</span> UserInfo("admin", "123456", "ROLE_ADMIN", <span style="color: #0000ff;">true</span>,<span style="color: #0000ff;">true</span>,<span style="color: #0000ff;">true</span>, <span style="color: #0000ff;">true</span><span style="color: #000000;">);
                  </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> userInfo;
                              
            }
            
            </span><span style="color: #0000ff;">return</span> <span style="color: #0000ff;">null</span>;</pre>
    
    View Code
    到这里为止,我们自己定义的UserInfo类和从数据库中返回具体的用户信息已经实现,接下来我们要实现的,我们自己的 AuthenticationProvider
    新建类 MyAuthenticationProvider 继承AuthenticationProvider
    完整的代码如下:
    @Component
    public class MyAuthenticationProvider implements AuthenticationProvider {
          /**
           * 注入我们自己定义的用户信息获取对象
           */
          @Autowired
          private UserDetailsService userDetailService;
          @Override
          public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                // TODO Auto-generated method stub
                String userName = authentication.getName();// 这个获取表单输入中返回的用户名;
                String password = (String) authentication.getPrincipal();// 这个是表单中输入的密码;
                // 这里构建来判断用户是否存在和密码是否正确
                UserInfo userInfo = (UserInfo) userDetailService.loadUserByUsername(userName); // 这里调用我们的自己写的获取用户的方法;
                if (userInfo == null) {
                      throw new BadCredentialsException("用户名不存在");
                }
                // //这里我们还要判断密码是否正确,实际应用中,我们的密码一般都会加密,以Md5加密为例
                // Md5PasswordEncoder md5PasswordEncoder=new Md5PasswordEncoder();
                // //这里第个参数,是salt
                // 就是加点盐的意思,这样的好处就是用户的密码如果都是123456,由于盐的不同,密码也是不一样的,就不用怕相同密码泄漏之后,不会批量被破解。
                // String encodePwd=md5PasswordEncoder.encodePassword(password, userName);
                // //这里判断密码正确与否
                // if(!userInfo.getPassword().equals(encodePwd))
                // {
                // throw new BadCredentialsException("密码不正确");
                // }
                // //这里还可以加一些其他信息的判断,比如用户账号已停用等判断,这里为了方便我接下去的判断,我就不用加密了。
                //
                //
                if (!userInfo.getPassword().equals("123456")) {
                      throw new BadCredentialsException("密码不正确");
                }
                Collection<? extends GrantedAuthority> authorities = userInfo.getAuthorities();
                // 构建返回的用户登录成功的token
                return new UsernamePasswordAuthenticationToken(userInfo, password, authorities);
          }
          @Override
          public boolean supports(Class<?> authentication) {
                // TODO Auto-generated method stub
                // 这里直接改成retrun true;表示是支持这个执行
                return true;
          }
    }
    View Code

    到此为止,我们的用户信息的获取,校验部分已经完成了。接下来要让它起作用,则我们需要在配置文件中修改,让他起作用。回到我的SecurityConfig代码文件,修改如下:

    1、注入我们自己的AuthenticationProvider
    2、修改配置的方法:
        @Autowired
        private AuthenticationProvider provider;  //注入我们自己的AuthenticationProvider
    
    
    @Override
    </span><span style="color: #0000ff;">protected</span> <span style="color: #0000ff;">void</span> configure(AuthenticationManagerBuilder auth) <span style="color: #0000ff;">throws</span><span style="color: #000000;"> Exception {
        </span><span style="color: #008000;">//</span><span style="color: #008000;"> TODO Auto-generated method stub</span>
    

    auth.authenticationProvider(provider);

    // auth
    // .inMemoryAuthentication()
    // .withUser("admin").password("123456").roles("USER")
    // .and()
    // .withUser("test").password("test123").roles("ADMIN");
    }

    View Code
    现在重新运行程序,则需要输入用户名为 admin 密码是123456之后,才能正常登录了。
    为了方便测试,我们调整添加另一个控制器 /whoim 的代码 ,让他返回当前登录的用户信息,前面说了,他是存在SecurityContextHolder 的全局变量中,所以我们可以这样获取
     
          @RequestMapping("/whoim")
          public Object whoIm()
          {
                return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
          }
    View Code

    我们运行,直接反问 /whoim ,则直接跳转到登录页面,我们验证过之后,再访问此url,结果如下:

    到这里,我们自定义的登录已经成功了。

    改造3、自定义登录成功和失败的处理逻辑

    在现在的大多数应用中,一般都是前后端分离的,所以我们登录成功或失败都需要用json格式返回,或者登录成功之后,跳转到某个具体的页面。
    接下来我们来实现这种改造。
     
    为了实现这个功能,我们需要写2个类,分别继承SavedRequestAwareAuthenticationSuccessHandlerSimpleUrlAuthenticationFailureHandler2个类,并重写其中的部分方法即可。
    //处理登录成功的。
    @Component("myAuthenticationSuccessHandler")
    public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler{
    
      @Autowired
      </span><span style="color: #0000ff;">private</span><span style="color: #000000;"> ObjectMapper objectMapper;
      @Override
      </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
                  </span><span style="color: #0000ff;">throws</span><span style="color: #000000;"> IOException, ServletException {            
            </span><span style="color: #008000;">//</span><span style="color: #008000;">什么都不做的话,那就直接调用父类的方法</span>
            <span style="color: #0000ff;">super</span><span style="color: #000000;">.onAuthenticationSuccess(request, response, authentication);  
            
            </span><span style="color: #008000;">//</span><span style="color: #008000;">这里可以根据实际情况,来确定是跳转到页面或者json格式。
            </span><span style="color: #008000;">//</span><span style="color: #008000;">如果是返回json格式,那么我们这么写</span>
    
    Map<String,String> map=new HashMap<>(); map.put("code", "200"); map.put("msg", "登录成功"); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(map));
            </span><span style="color: #008000;">//</span><span style="color: #008000;">如果是要跳转到某个页面的,比如我们的那个whoim的则</span>
            <span style="color: #0000ff;">new</span> DefaultRedirectStrategy().sendRedirect(request, response, "/whoim"<span style="color: #000000;">);
            
      }
    

    }

    View Code
    //登录失败的
    @Component("myAuthenticationFailHander")
    public class MyAuthenticationFailHander extends SimpleUrlAuthenticationFailureHandler {
          @Autowired
          private ObjectMapper objectMapper;
          private Logger logger = LoggerFactory.getLogger(getClass());
          @Override
          public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                      AuthenticationException exception) throws IOException, ServletException {
                // TODO Auto-generated method stub
                logger.info("登录失败");
                //以Json格式返回
                Map<String,String> map=new HashMap<>();
                map.put("code", "201");
                map.put("msg", "登录失败");
                response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
                response.setContentType("application/json");
                response.setCharacterEncoding("UTF-8");   
                response.getWriter().write(objectMapper.writeValueAsString(map));
    
      }
    

    }

    View Code
    代码完成之后,修改配置config类代码。
    添加2个注解,自动注入
          @Autowired
          private AuthenticationSuccessHandler myAuthenticationSuccessHandler;
          @Autowired
          private AuthenticationFailureHandler myAuthenticationFailHander;
    
      @Override
      </span><span style="color: #0000ff;">protected</span> <span style="color: #0000ff;">void</span> configure(HttpSecurity http) <span style="color: #0000ff;">throws</span><span style="color: #000000;"> Exception {
            </span><span style="color: #008000;">//</span><span style="color: #008000;"> TODO Auto-generated method stub
            </span><span style="color: #008000;">//</span><span style="color: #008000;">super.configure(http);</span>
    

    http
    .formLogin().loginPage(
    "/login").loginProcessingUrl("/login/form")
    .successHandler(myAuthenticationSuccessHandler)
    .failureHandler(myAuthenticationFailHander)
    .permitAll()
    //表单登录,permitAll()表示这个不需要验证 登录页面,登录失败页面
    .and()
    .authorizeRequests().anyRequest().authenticated()
    .and()
    .csrf().disable();
    }

    View Code

    进行测试,我们先返回json格式的(登录成功和失败的)

       

    改成跳转到默认页面

    改造4、添加权限控制

    之前的代码我们用户的权限没有加以利用,现在我们添加权限的用法。
    之前的登录验证通俗的说,就是来判断你是谁(认证),而权限控制就是用来确定:你能做什么或者不能做什么(权限)
     
    在讲这个之前,我们简单说下,对于一些资源不需要权限认证的,那么就可以在Config中添加 过滤条件,如:
    @Override
          protected void configure(HttpSecurity http) throws Exception {
                // TODO Auto-generated method stub
                //super.configure(http);
                http
                      .formLogin().loginPage("/login").loginProcessingUrl("/login/form")
                      .successHandler(myAuthenticationSuccessHandler)
                      .failureHandler(myAuthenticationFailHander)
                      .permitAll()  //表单登录,permitAll()表示这个不需要验证 登录页面,登录失败页面
                      .and()
                      .authorizeRequests()
                            .antMatchers("/index").permitAll()  //这就表示 /index这个页面不需要权限认证,所有人都可以访问
                      .anyRequest().authenticated()             
                      .and()
                      .csrf().disable();            
          }
    View Code
    那么我们直接访问 /index 就不会跳转到登录页面,这样我们就可以把一些不需要验证的资源以这种方式过滤,比如图片,脚本,样式文件之类的。
    我们先来看第一种:在编码中写死的。
    那其实权限控制也是通过这种方式来实现:
       http
                      .formLogin().loginPage("/login").loginProcessingUrl("/login/form")
                      .successHandler(myAuthenticationSuccessHandler)
                      .failureHandler(myAuthenticationFailHander)
                      .permitAll()  //表单登录,permitAll()表示这个不需要验证 登录页面,登录失败页面
                      .and()
                      .authorizeRequests()
                            .antMatchers("/index").permitAll()                    
                      .antMatchers("/whoim").hasRole("ADMIN") //这就表示/whoim的这个资源需要有ROLE_ADMIN的这个角色才能访问。不然就会提示拒绝访问
                      .anyRequest().authenticated() //必须经过认证以后才能访问          
                      .and()
                      .csrf().disable();   
    View Code

    这个用户的角色哪里来,就是我们自己的UserDetailsService中返回的用户信息中的角色权限信息,这里需要注意一下就是 .hasRole("ADMIN"),那么给用户的角色时就要用:ROLE_ADMIN 

    .antMatchers 这里也可以限定HttpMethod的不同要求不同的权限(用于适用于Restful风格的API).
    如:Post需要 管理员权限,get 需要user权限,我们可以这么个改造,同时也可以通过通配符来是实现 如:/user/1 这种带参数的URL
    .antMatchers("/whoim").hasRole("ADMIN")
          .antMatchers(HttpMethod.POST,"/user/*").hasRole("ADMIN")
          .antMatchers(HttpMethod.GET,"/user/*").hasRole("USER")
     
    Spring Security 的校验的原理:左手配置信息,右手登录后的用户信息,中间投票器。
     从我们的配置信息中获取相关的URL和需要的权限信息,然后获得登录后的用户信息,
    然后经过:AccessDecisionManager 来验证,这里面有多个投票器:AccessDecisionVoter,(默认有几种实现:比如:1票否决(只要有一个不同意,就没有权限),全票通过,才算通过;只要有1个通过,就全部通过。类似这种的。
    WebExpressionVoter 是Spring Security默认提供的的web开发的投票器。(表达式的投票器
     
    Spring Security 默认的是 AffirmativeBased   只要有一个通过,就通过。
    有兴趣的可以 从FilterSecurityInterceptor这个过滤器入口,来查看这个流程。
    内嵌的表达式有:permitAll  denyAll   等等。
    每一个权限表达式都对应一个方法。
    如果需要同时满足多个要求的,不能连写如 ,我们有个URL需要管理员权限也同时要限定IP的话,不能:.hasRole("ADMIN").hasIPAddress("192.168.1.1"); 
    而是需要用access方法    .access("hasRole('ADMIN') and hasIpAddress('192.168.1.1')");这种。
     
    那我们可以自己写权限表达式吗? 可以,稍后。。。这些都是硬编码的实现,都是在代码中写入的,这样的灵活性不够。所以我们接下来继续改造
    改造4、添加基于RBAC(role-Based-access control)权限控制
    这个大家可以去百度一下,一般都是由 3个部分组成,一个是用户,一个是角色 ,一个是资源(菜单,按钮),然后就是 用户和角色的关联表,角色和资源的关联表
     
    核心就是判断当前的用户所拥有的URL是否和当前访问的URL是否匹配。
    首先我们自己提供一个判断的接口和实现,代码如下:
    /**
     * 返回权限验证的接口
     * 
     *
     */
    public interface RbacService {
          boolean hasPermission(HttpServletRequest request,Authentication authentication);
    }
    

    @Component("rbacService")
    public class RbacServiceImpl implements RbacService {
    private AntPathMatcher antPathMatcher = new AntPathMatcher();
    @Override
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
    Object principal
    = authentication.getPrincipal();
    boolean hasPermission = false;
    if (principal instanceof UserDetails) { //首先判断先当前用户是否是我们UserDetails对象。
    String userName = ((UserDetails) principal).getUsername();
    Set
    <String> urls = new HashSet<>(); // 数据库读取 //读取用户所拥有权限的所有URL

    urls.add(
    "/whoim");
    // 注意这里不能用equal来判断,因为有些URL是有参数的,所以要用AntPathMatcher来比较
    for (String url : urls) {
    if (antPathMatcher.match(url, request.getRequestURI())) {
    hasPermission
    = true;
    break;
    }
    }
    }
    return hasPermission;
    }
    }

    View Code

    然后在Security的配置项中添加自定义的权限表达式就可以了。

    @Override
          protected void configure(HttpSecurity http) throws Exception {
                // TODO Auto-generated method stub
                //super.configure(http);
                http
                      .formLogin().loginPage("/login").loginProcessingUrl("/login/form")
                      .successHandler(myAuthenticationSuccessHandler)
                      .failureHandler(myAuthenticationFailHander)
                      .permitAll()  //表单登录,permitAll()表示这个不需要验证 登录页面,登录失败页面
                      .and()
                      .authorizeRequests()
    //                      .antMatchers("/index").permitAll()                    
    //                .antMatchers("/whoim").hasRole("ADMIN")
    //                .antMatchers(HttpMethod.POST,"/user/*").hasRole("ADMIN")
    //                .antMatchers(HttpMethod.GET,"/user/*").hasRole("USER")
                      .anyRequest().access("@rbacService.hasPermission(request,authentication)")    //必须经过认证以后才能访问            
                      .and()
                      .csrf().disable();            
          }
    View Code

    其中 @rbacService 就是我们自己声明的bean,在RbacServiceImpl实现类的头部注解中。

    改造5、记住我的功能Remeber me

    本质是通过token来读取用户信息,所以服务端需要存储下token信息
    根据官方的文档,token可以通过数据库存储 数据库脚本
    复制代码
    CREATE TABLE persistent_logins (
        username VARCHAR(64) NOT NULL,
        series VARCHAR(64) NOT NULL,
        token VARCHAR(64) NOT NULL,
        last_used TIMESTAMP NOT NULL,
        PRIMARY KEY (series)
    );
    复制代码

    然后,配置好token 的存储 及数据源

     @Autowired
          private DataSource dataSource;   //是在application.properites
    
      <span style="color: #008000;">/**</span><span style="color: #008000;">
       * 记住我功能的token存取器配置
       * </span><span style="color: #808080;">@return</span>
       <span style="color: #008000;">*/</span><span style="color: #000000;">
      @Bean
      </span><span style="color: #0000ff;">public</span><span style="color: #000000;"> PersistentTokenRepository persistentTokenRepository() {
            JdbcTokenRepositoryImpl tokenRepository </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> JdbcTokenRepositoryImpl();
            tokenRepository.setDataSource(dataSource);
            </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> tokenRepository;
      }</span></pre>
    
    View Code

    修改Security配置

      @Override
          protected void configure(HttpSecurity http) throws Exception {
                // TODO Auto-generated method stub
                //super.configure(http);
                http
                      .formLogin().loginPage("/login").loginProcessingUrl("/login/form")
                      .successHandler(myAuthenticationSuccessHandler)
                      .failureHandler(myAuthenticationFailHander)
                      .permitAll()  //表单登录,permitAll()表示这个不需要验证 登录页面,登录失败页面
                      .and()
                      .rememberMe()
                            .rememberMeParameter("remember-me").userDetailsService(userDetailsService)
                            .tokenRepository(persistentTokenRepository())
                            .tokenValiditySeconds(60)
                      .and()
                      .authorizeRequests()
    //                      .antMatchers("/index").permitAll()                    
    //                .antMatchers("/whoim").hasRole("ADMIN")
    //                .antMatchers(HttpMethod.POST,"/user/*").hasRole("ADMIN")
    //                .antMatchers(HttpMethod.GET,"/user/*").hasRole("USER")
                      .anyRequest().access("@rbacService.hasPermission(request,authentication)")    //必须经过认证以后才能访问            
                      .and()
                      .csrf().disable();      
    View Code
    登录之后 数据库就会有一条数据
    然后,服务重新启动下,我们在看下直接访问 /whoim 的话,就可以直接访问了,不需要再登录了。
     
    到此为止我们的Spring Securtiy 的基本用法已经改造完成了。
    接下来,我会继续学习下Spring Security Oauth2 的内容,敬请期待。
    原文地址:http://www.cnblogs.com/SmallTalker/p/7851848.html
  • 相关阅读:
    yum 下载安装包以及依赖包
    《将博客搬至CSDN》
    Lucene
    Solr
    LVS原理详解(3种工作模式及8种调度算法)
    正向代理与反向代理
    网关,网卡
    NAT地址转换
    Nginx学习总结
    网络_OSI模型_数据包传输
  • 原文地址:https://www.cnblogs.com/jpfss/p/9045771.html
Copyright © 2011-2022 走看看