【Spring Security】005-Spring Security web权限方案(3):用户注销、自动登录、CSRF功能

目录

一、用户注销

1、用户注销实现步骤

第一步:在配置类MySecurityConfig中添加退用户注销代码

第二步:添加success.html作为登录成功页,里面添加退出链接

第三步:在配置类MySecurityConfig中修改登录之后跳转的页面

第四步:重启项目,访问测试

二、自动登录

相关技术:

实现原理*:

代码实现:

三、CSRF功能

1、CSRF理解

2、Spring Security实现CSRF功能原理


一、用户注销

1、用户注销实现步骤

第一步:在配置类MySecurityConfig中添加退用户注销代码

// 注销:注销访问的地址,注销后跳转到的页面
http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();

 

第二步:添加success.html作为登录成功页,里面添加退出链接

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>登录成功!</h1>
    <a href="/logout">注销!</a>
</body>
</html>

 

第三步:在配置类MySecurityConfig中修改登录之后跳转的页面

 

第四步:重启项目,访问测试

访问http://localhost:8111/login.html进行登录

登录成功:

注销测试:

注销结果验证:

访问http://localhost:8111/index

 

二、自动登录

相关技术:

Cookie技术(数据保存在浏览器,存在安全隐患);

安全框架机制实现自动登录;

 

实现原理*:

 

代码实现:

第一步:创建数据库表(可以自动生成)

CREATE TABLE `persistent_logins` (
	`username` VARCHAR ( 64 ) NOT NULL,
	`series` VARCHAR ( 64 ) NOT NULL,
	`token` VARCHAR ( 64 ) NOT NULL,
	`last_used` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY ( `series` ) 
) ENGINE = INNODB DEFAULT CHARSET = utf8;

第二步:在配置类中注入数据源,配置操作的数据库对象

我们就直接在MySecurityConfig进行配置了

package com.zibo.studyspringsecurity.config;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.sql.DataSource;

@Configuration
@MapperScan("com.zibo.studyspringsecurity.mapper")
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private DataSource dataSource; // 注入数据源

    // 配置对象
    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        // 自动生成表的代码,我们手动创建了,就不配置了
        // jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    // 注入 PasswordEncoder 类到 spring 容器中
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 注销:注销访问的地址,注销后跳转到的页面
        http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();
        // 无权限访问,跳转到自定义的403页面
        http.exceptionHandling().accessDeniedPage("/unauth.html");
        http.formLogin() // 自定义自己编写的登录页面
            // 登录页面设置
            .loginPage("/login.html")
            // 登录访问的路径,提交到的controller,不需要自己写
            .loginProcessingUrl("/user/login")
            // 登陆成功之后要跳转到的路径
            .defaultSuccessUrl("/success.html").permitAll()
            // 设置哪些路径不需要登陆就可以访问
            .and().authorizeRequests().antMatchers("/","/test/hello","/user/login").permitAll()
                // 表示当前用户只有具有admins权限才能访问/test/index路径
                // .antMatchers("/test/index").hasAuthority("admins")
                // 指定多个权限可访问,有其中任意一个权限即可访问
                // .antMatchers("/test/index").hasAnyAuthority("admins,manager")
                // 指定sale角色可访问此路径
                // .antMatchers("/test/index").hasRole("sale")
                // 指定哪些角色可访问此路径
                .antMatchers("/test/index").hasAnyRole("sale,buy")
            .anyRequest().authenticated()
            .and().csrf().disable(); // 关闭csrf防护
    }
}

第三步:在配置类MySecurityConfig中配置自动登录

图示:

代码:

package com.zibo.studyspringsecurity.config;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.sql.DataSource;

@Configuration
@MapperScan("com.zibo.studyspringsecurity.mapper")
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private DataSource dataSource; // 注入数据源

    // 配置对象
    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        // 自动生成表的代码,我们手动创建了,就不配置了
        // jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    // 注入 PasswordEncoder 类到 spring 容器中
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 注销:注销访问的地址,注销后跳转到的页面
        http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();
        // 无权限访问,跳转到自定义的403页面
        http.exceptionHandling().accessDeniedPage("/unauth.html");
        http.formLogin() // 自定义自己编写的登录页面
            // 登录页面设置
            .loginPage("/login.html")
            // 登录访问的路径,提交到的controller,不需要自己写
            .loginProcessingUrl("/user/login")
            // 登陆成功之后要跳转到的路径
            .defaultSuccessUrl("/success.html").permitAll()
            // 设置哪些路径不需要登陆就可以访问
            .and().authorizeRequests().antMatchers("/","/test/hello","/user/login").permitAll()
                // 表示当前用户只有具有admins权限才能访问/test/index路径
                // .antMatchers("/test/index").hasAuthority("admins")
                // 指定多个权限可访问,有其中任意一个权限即可访问
                // .antMatchers("/test/index").hasAnyAuthority("admins,manager")
                // 指定sale角色可访问此路径
                // .antMatchers("/test/index").hasRole("sale")
                // 指定哪些角色可访问此路径
                .antMatchers("/test/index").hasAnyRole("sale,buy")
                .anyRequest().authenticated()
                .and().rememberMe().tokenRepository(persistentTokenRepository())
                .tokenValiditySeconds(60) // 时间,单位是秒
                .userDetailsService(userDetailsService)
            .and().csrf().disable(); // 关闭csrf防护
    }
}

第四步:在登陆页面login.html中添加自动登录的选项

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form method="post" action="/user/login">
        用户名:<input type="text" name="username"/>
        密码:<input type="text" name="password"/>
        <input type="submit" value="登录"/>
        <!--name="remember-me",这个名字是固定的!-->
        <input type="checkbox" name="remember-me"/>自动登录
    </form>
</body>
</html>

第五步:重启项目,测试

访问http://localhost:8111/login.html进行登录,并勾选自动登陆选项

重启项目,访问http://localhost:8111/test/index

60秒之后,再次重启项目,再次访问http://localhost:8111/test/index

数据库里面也有一条登录记录:

 

三、CSRF功能

1、CSRF理解

跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任;

跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了 web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的;

从 Spring Security 4.0 开始,默认情况下会启用 CSRF 保护,以防止 CSRF 攻击应用程序,Spring Security CSRF 会针对 PATCH,POST,PUT 和 DELETE 方法进行防护;

 

2、Spring Security实现CSRF功能原理

其他参考文章:https://www.cnblogs.com/june777/p/11996409.html

一个用户通过浏览器成功登录一个网站,登陆成功后,服务器会返回一个该用户的唯一标识放入浏览器Cookie中,以此作为用户之后操作的唯一凭证。假设此时该用户在此网站中请求一个表单类的网页,这时候用户又打开了另外的一个网站,而这个网站是一个病毒网站,它直接窃取了Cookie信息,当然也包括唯一身份凭证(所以为什么说cookie不推荐保存重要信息,是有原因的),通过唯一身份凭证,病毒网站直接进行用户所做的表单提交,而服务器是通过这个凭证来匹配用户信息的,服务器这时候无法识别病毒网站所做的操作,误以为是用户操作,此时可能造成用户严重损失。 Spring Security作为Spring提供的一个安全框架,使用它时,其内部便已经对CSRF攻击做了相关的防止工作(除了异步请求)。 下面说一下Security对CSRF攻击所作的相关工作: 继续上图,然后解释:

当你在项目中导入了插件后,整个项目就直接被Security管理。之后我们再次登录相关网站,尔后在网站上请求一个会返回表单类的网页,这时候服务器不仅仅会存入一个带唯一标识的Cookie信息,还会带上一个类型为hidden的隐藏输入框,里面封装的就是security提供的另外一个随机唯一身份标识,如图:

这个value值是随机生成的,而这时病毒网站可以窃取到用户唯一标识,却无法知道给该用户的随机token,这就能防止csrf攻击所造成的影响。 但是有一些情况,security是无法做处理的,比如异步请求,这时候我们需要在代码中做一些处理配置,才能达到相关的防止工作的要求。 下面演示security在异步请求的情况下对防止csrf攻击的一些处理: 首先需要在触发异步请求的前端界面上(index.html)设置以下数据:

	<!--访问该页面时,在此处生成CSRF令牌.-->
	<meta name="_csrf" th:content="${_csrf.token}">
	<meta name="_csrf_header" th:content="${_csrf.headerName}">

尔后在发送异步请求的js界面设置:

    发送AJAX请求之前,将CSRF令牌设置到请求的消息头中.
    var token = $("meta[name='_csrf']").attr("content");
    var header = $("meta[name='_csrf_header']").attr("content");
    $(document).ajaxSend(function(e, xhr, options){
        xhr.setRequestHeader(header, token);
    });

当刷新当前界面时,会得到token随机标识:

之后在提交ajax请求的时候会带着随机生成的token发送给服务器。

这里我们不再进行繁琐的代码演示,在实际需要的时候再找相关博客探究细节;

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值