spring security

103 阅读5分钟
  1. 导入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    
  2. 实现自定义逻辑

  • 编写一个类实现UserDetailsService接口

  • 重写loadUserByUsername方法

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
    	根据用户名从数据库查询用户对象
    	根据用户ID从数据库查询用户角色与用户权限
    User(username,password,AuthorityUtils.commaSeparatedStringToAuthorityList(“”))
    // 在给用户赋予角色时角色需要以:ROLE_ 开头,后面添加角色名称
    }
    返回值:UserDetails的实现类 org.springframework.security.core.userdetails.User
    参数:客户端表单传递过来的数据,必须叫username
    异常:UsernameNotFoundException 用户名没有发现异常
    
  1. 创建配置类

    @Configuration
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            /*
             * forLogin()表示手动配置认证逻辑
             *     loginPage()设置登录页面是什么
             *     successForwardUrl()设置登录请求逻辑
             *     successForwardUrl()设置认证成功后主动跳转的控制器路径
             *                    默认是上一个页面请求什么,认证成功后跳转那个页面
             *                    如果是直接登录认证,成功后访问:  index.jsp/index.html
             *                    如果设置successForwardUrl(), 则认证成功后跳转该请求的控制器
             *     failureForwardUrl()设置认证失败后主动跳转的控制器路径
             *      usernameParameter("uname") 修改username的请求参数
             *      passwordParameter("pwd")   修改password的请求参数
             *
             */
    
            http.formLogin()
                    .loginProcessingUrl("/login")
                    .successForwardUrl("/main")
                    .failureForwardUrl("/fail")
                    //.usernameParameter("uname")
                    //.passwordParameter("pwd")
                    .loginPage("/login.html");
    
            /**
             * authorizeRequests()设置请求权限
             *    antMatchers("/login.html").permitAll()
             *    表示login.html所有请求都可以访问,不需要被认证
             *     antMatchers("/login.html").anonymous()
             *    表示login.html路径可以匿名访问
             *    anyRequest().authenticated() 相当于 antMatchers("/**") 表示所有请求必须先认证
             */
    
            http.authorizeRequests()
                    .antMatchers("/login.html").permitAll()
                    .antMatchers("/fail.html").permitAll()
                    .antMatchers("/js/**","/css/**").permitAll()
                    .anyRequest().authenticated();
    
            // 关闭csrf保护
            http.csrf().disable();
        }
    
        /**
         * 密码凭证器
         * @return BCryptPasswordEncoder
         */
        @Bean
        public BCryptPasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
    }
    
  • 内置控制方法介绍

    * permitAll()  表示所匹配的URL任何人都允许访问
    
    * authentication() 表示所匹配的URL都需要被认证才能访问
    
    * anonymous() 表示可以匿名访问匹配的URL
    
    * denyAll() 表示所匹配的URL都不允许被访问
    
    * rememberMe() 被“remember me”的用户允许访问
    
    * fullyAuthentication() 如果用户不是被remember me的,才可以访问
    
  • 角色权限判断

    * hasAuthority(String)    
      * 判断用户是否具有特定的权限
      *  .antMatchers("/user.html").hasAuthority("/user")
    * hasAnyAuthority(String)   
      * 如果用户具备给定权限中某一个,就允许访问
      * .antMatchers("/product.html").hasAnyAuthority("/product", "/order")
    * hasRole(String)
      * 如果用户具备给定角色就允许访问。否则出现403。
      * 在给用户赋予角色时角色需要以:ROLE_ 开头,后面添加角色名称。例如:ROLE_abc 其中abc是角色名,ROLE_是固定的字符开头。使用hasRole()时参数也只写abc即可。否则启动报错。
      * .antMatchers("/system.html").hasRole("管理员")
    * hasAnyRole(String ...)
      * 如果用户具备给定角色的任意一个,就允许被访问
    * hasIpAddress(String)
      * 如果请求是指定的IP就运行访问。
    
  • 基于注解的访问控制

    *  在启动类(也可以在配置类等能够扫描的类上)上添加
         @EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
    * 在控制器的方法上添加@Secured注解
      @Secured("ROLE_abc")
      @RequestMapping("/toMain")
      public String toMain(){  
          return "redirect:/main.html";
      }
    *  @PreAuthorize表示访问方法或类在执行之前先判断权限,大多情况下都是使用这个注解
        @PreAuthorize("hasRole('abc')")
        @RequestMapping("/toMain")
        public String toMain(){
            return "redirect:/main.html";
        }
    * @PostAuthorize表示方法或类执行结束后判断权限,此注解很少被使用到。
    
  • Remember Me功能实现

    * Spring Security会自动把用户信息存储到数据源中,以后就可以不登录进行访问
    
    * 编写配置类
     @Configuration
      public class RememberMeConfig {
          @Autowired
          private DataSource dataSource;
          @Bean
          public PersistentTokenRepository getPersistentTokenRepository() {
              JdbcTokenRepositoryImpl jdbcTokenRepositoryImpl=new JdbcTokenRepositoryImpl();
              jdbcTokenRepositoryImpl.setDataSource(dataSource);
              //自动建表,第一次启动时需要,第二次启动时注释掉
      // jdbcTokenRepositoryImpl.setCreateTableOnStartup(true);
              return jdbcTokenRepositoryImpl;
          }
      }
    
    * 修改SecurityConfig
    
      * 注入 private PersistentTokenRepository persistentTokenRepository;
    
      * 注入 private UserDetailsService userDetailsService;
        http.rememberMe()
    	.tokenValiditySeconds(120)//单位:秒 , 有效时间,默认2周 
            .userDetailsService(userDetailsService) //登录逻辑交给哪个对象
            .tokenRepository(persistentTokenRepository);   //持久层对象
    ```
    * 在客户端页面中添加复选框
    <input type="checkbox" name="remember-me" value="true"/>
    

hymeleaf中Spring Security的使用

  • 在html页面中引用thymeleaf命名空间和security命名空间

    html页面中引用thymeleaf命名空间和security命名空间
    
  • name:登录账号名称

    登录账号:<span sec:authentication="name"></span>
    
  • principle : 登录主体 , 在自定义登录逻辑中是UserDetails

     登录账号:<span sec:authentication="principal.username"></span>
    
  • credentials : 凭证

    凭证:<span sec:authentication="credentials"></span>
    
  • authorities : 权限和角色

    权限和角色:<span sec:authentication="authorities"></span>
    
  • details : 实际上是WebAuthenDetails的实例 , 可以获取remoteAddress(客户端ip)和sessionId(当前sessionId)

    客户端地址:<span sec:authentication="details.remoteAddress">456</span><br/>
    sessionId:<span sec:authentication="details.sessionId">456</span><br/>
    
  • 在页面中根据用户权限和角色判断页面中显示的内容

    通过权限判断:
    <button sec:authorize="hasAuthority('/insert')">新增</button>
    <button sec:authorize="hasAuthority('/delete')">删除</button>
    <button sec:authorize="hasAuthority('/update')">修改</button>
    <button sec:authorize="hasAuthority('/select')">查看</button>
    <br/>
    通过角色判断:
    <button sec:authorize="hasRole('abc')">新增</button>
    <button sec:authorize="hasRole('abc')">删除</button>
    <button sec:authorize="hasRole('abc')">修改</button>
    <button sec:authorize="hasRole('abc')">查看</button>
    

退出登录

  • 在页面中添加/logout的超链接即可

    <a href="/logout">退出登录</a>
    
  • 修改SecurityConfig

    http.logout()
            .logoutUrl("/logout")
            .logoutSuccessUrl("/login.html");
    

Spring Security中CSRF

  • CSRF(Cross-site request forgery)跨站请求伪造

    • 跨域:只要网络协议,ip地址,端口中任何一个不相同就是跨域请求。

    • 在跨域的情况下,session id可能被第三方恶意劫持(session id是 cookie中存放用来识别客户端身份的)

    • 通过伪造用户请求访问受信任站点的非法请求访问

  • 从Spring Security4开始CSRF防护默认开启。默认会拦截请求。

  • CSRF为了保证不是其他第三方网站访问,要求访问时携带参数名为_csrf值为token(token在服务端产生)的内容,如果token和服务端的token匹配成功,则正常访问。

  • 开启csrf防护情况下,修改登录页面

    <form action = "/login" method="post">
        <input type="hidden" th:value="${_csrf.token}" name="_csrf" th:if="${_csrf}"/>
        用户名:<input type="text" name="username"/><br/>
        密码:<input type="password" name="password"/><br/>
        <input type="submit" value="登录"/>
    </form>
    
  • 如果我们开启了CSRF保护机制,则默认情况下,不能使用get方式的/logout

  • 使用form表单退出

    <form style="float: right" action="/logout" method="post">
        <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
        <button type='submit'>退出</button>
    </form>