Spring Security学习笔记

目录

1.基础概念

1)认证授权会话

2)授权的数据模型

3)RBAC

2.Spring Security简单实现

3.UserDetailsService

4.PassworldEncoder

5.自定义登录页面

6.自定义跳转

7.响应异常信息

8.RememberMe功能



1.基础概念

1)认证授权会话

认证概念:

认证就是对用户的身份进行确认,比如我们登录QQ需要输入账号和密码,而这个过程就是认证.

用户认证就是判断一个用户的身份是否合法的过程,用户去访问系统资源时要求验证用户的身份信息,身份合法可以继续访问,不合法则拒绝访问,常见的用户身份验证方式有:用户密码登录,二维码登录,手机短信登录,指纹认证等方式.

会话概念:

用户通过认证后,为了避免用户每次操作都进行认证,可以将用户的信息保存在会话中.

会话就是系统为了保持当前用户登录状态所提供的机制,常见的有基于session方式和token方式.

授权概念:

授权是用户认证通过后,根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有则拒绝访问.

认证是为了保护用户身份的合法性,授权则是为了更细粒度的对隐私数据进行划分,授权是在认证通过后发生的,控制不同的用户能访问不同的资源.

2)授权的数据模型

如何进行授权就是如何对用户访问资源进行控制,首先需要学习授权相关的数据模型.

授权可简单理解为Who 对 What 进行 How 操作

Who:是主体(Subject),主体一般指用户,也可以是程序,需要访问系统中的资源.

What:资源(Resource),如系统菜单,页面,按钮,代码方法,系统商品信息,系统订单信息等.

How:权限/许可(Permission),规定了用户对资源的操作许可,权限离开资源没有意义,如用户查询权限等.

Spring Security学习笔记_第1张图片

主体(用户id,账号,密码,...)

权限(权限id,权限标识,权限名称,资源id,...)

资源(资源id,资源名称,访问地址,...)

角色(角色id,角色名称,...)

角色和权限关系(角色id,权限id,...)

主体(用户)和角色关系(用户id,角色id,...)

同时资源可以分为功能资源和数据资源,举个例子,我要查询商品订单就是数据资源,而我要进行消息发布则是功能资源.

主体(用户),资源,权限关系图:

Spring Security学习笔记_第2张图片

 在实际开发中,有时也可以将资源和权限合并

权限(权限id,权限标识,权限名称,资源名称,资源访问地址,....)

3)RBAC

RBAC基于角色的访问控制(Role-Based Access Control),是按照角色进行授权,比如主体的角色为总经理可以查询员工信息.

伪代码如下:

查询工资信息

判断是否是总经理

如果是则继续访问,如果不是则无权访问处理

但是需要进行修改角色权限时就需要修改授权相关代码,系统可扩展性差.

RBAC基于角色的访问控制(Role-Based Access Control),是按照资源(或权限)进行授权,比如用户必须具有查询工资的权限才可以进行查询.

伪代码如下:

查询工资信息

判断主体是否拥有查询工资权限

有则访问,无则拒绝

用户可以按照角色来判断,或者也可以通过资源(权限).

2.Spring Security简单实现

1)导入依赖

Spring Security依赖


			org.springframework.boot
			spring-boot-starter-security
		
		


			org.springframework.security
			spring-security-test
			test
		

2)编写一个Controller类

@RestController
@SpringBootApplication
public class SecstudentApplication {

	@GetMapping("/")
	public String hello(){
		return "hello,spring security";
	}

	public static void main(String[] args) {
		SpringApplication.run(SecstudentApplication.class, args);
	}

}

3)启动项目

当项目启动的时候,如果我们没配置security账户密码,默认为user和控制台生成的密码.

访问8080端口,会出现验证页面,这是security自带的默认页面.

Spring Security学习笔记_第3张图片

 输入相应的账户和密码才能进行访问.

3.UserDetailsService

通常我们不可能将账户密码写在配置文件中,也不显示,我们都是通过去数据库拿到账户和密码然后进行验证.

所以必须通过我们自定义逻辑来校验账户和密码.

我们可以通过实现UserDetailsService来实现.

package org.springframework.security.core.userdetails;

public interface UserDetailsService {

    //这个方法是通过用户名加载用户的一个方法,返回了一个UserDetails
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

在实现UserDetailsService的时候,我们还要返回一个UserDetails

UserDetails也是一个接口,它里面定义了各种返回用户信息和权限的方法:

public interface UserDetails extends Serializable {

    //返回获取的所有权限,但是不能返回空
    //包含用户的所有权限,当某功能需要权限,但是没有时就会出现404等跳转页面
    Collection getAuthorities();

    //返回密码
    String getPassword();

    //返回用户名
    String getUsername();

    //判断用户是否过期,过期的账号不能用来登录
    boolean isAccountNonExpired();

    //判断用户是否被锁定,被锁定的用户也不能用来登录
    boolean isAccountNonLocked();

    //判断用户凭证是否过期
    boolean isCredentialsNonExpired();

    //判断账户是否可用
    boolean isEnabled();
}

UserDetetails是一个接口,必须要有类来实现该接口才能使用,而security提供了一个默认实现类User,User里面存放着用户的信息和权限.

public class User implements UserDetails, CredentialsContainer {
    private static final long serialVersionUID = 600L;
    private static final Log logger = LogFactory.getLog(User.class);
    private String password;
    private final String username;
    private final Set authorities;
    private final boolean accountNonExpired;
    private final boolean accountNonLocked;
    private final boolean credentialsNonExpired;
    private final boolean enabled;

    //传入用户名,密码,权限列表
    /**
    *  注意!!!!这里只有用户名是前端传过来的用户名
    *  而密码是从数据库中进行查询后返回的一个密码
    *  它会将我们从数据库中拿到的密码跟前端传来的密码进行比较
    *  如果成功认证通过,否则失败
    */
    public User(String username, String password, Collection authorities) {
        this(username, password, true, true, true, true, authorities);
    }

    //这个构造方法会对用户名和密码进行判断等等
    public User(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection authorities) {
        Assert.isTrue(username != null && !"".equals(username) && password != null, "Cannot pass null or empty values to constructor");
        this.username = username;
        this.password = password;
        this.enabled = enabled;
        this.accountNonExpired = accountNonExpired;
        this.credentialsNonExpired = credentialsNonExpired;
        this.accountNonLocked = accountNonLocked;
        this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
    }
//其他方法....
}

如果我们通过重写UserDetailsService的loadUserByUserName的自定义逻辑方法没认证/查找到这个用户,则会抛出UsernameNotFoundException异常,标识没有找到这个用户.

4.PassworldEncoder

在进行验证的时候,我们需要对密码进行加密,同时我们存储在数据库中的密码也应该是加密的,而security提供了一个加密接口

public interface PasswordEncoder {
    String encode(CharSequence rawPassword);

    boolean matches(CharSequence rawPassword, String encodedPassword);

    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}

该接口定义了两个主要方法,一个是encode(加密方法),另一个是matches对比方法,而我们也不需要自己去写加密类,只需要使用提供的默认实现类就行了.

推荐使用BCryptPasswordEncoder来进行加密.

public class BCryptPasswordEncoder implements PasswordEncoder {
    private Pattern BCRYPT_PATTERN;
    private final Log logger;
    private final int strength;
    private final BCryptPasswordEncoder.BCryptVersion version;
    private final SecureRandom random;

    public BCryptPasswordEncoder() {
        this(-1);
    }

    public BCryptPasswordEncoder(int strength) {
        this(strength, (SecureRandom)null);
    }

    public BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion version) {
        this(version, (SecureRandom)null);
    }

    public BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion version, SecureRandom random) {
        this(version, -1, random);
    }

    public BCryptPasswordEncoder(int strength, SecureRandom random) {
        this(BCryptPasswordEncoder.BCryptVersion.$2A, strength, random);
    }

    public BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion version, int strength) {
        this(version, strength, (SecureRandom)null);
    }

    public BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion version, int strength, SecureRandom random) {
        this.BCRYPT_PATTERN = Pattern.compile("\\A\\$2(a|y|b)?\\$(\\d\\d)\\$[./0-9A-Za-z]{53}");
        this.logger = LogFactory.getLog(this.getClass());
        if (strength == -1 || strength >= 4 && strength <= 31) {
            this.version = version;
            this.strength = strength == -1 ? 10 : strength;
            this.random = random;
        } else {
            throw new IllegalArgumentException("Bad strength");
        }
    }

    public String encode(CharSequence rawPassword) {
        if (rawPassword == null) {
            throw new IllegalArgumentException("rawPassword cannot be null");
        } else {
            String salt = this.getSalt();
            return BCrypt.hashpw(rawPassword.toString(), salt);
        }
    }

    private String getSalt() {
        return this.random != null ? BCrypt.gensalt(this.version.getVersion(), this.strength, this.random) : BCrypt.gensalt(this.version.getVersion(), this.strength);
    }

    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        if (rawPassword == null) {
            throw new IllegalArgumentException("rawPassword cannot be null");
        } else if (encodedPassword != null && encodedPassword.length() != 0) {
            if (!this.BCRYPT_PATTERN.matcher(encodedPassword).matches()) {
                this.logger.warn("Encoded password does not look like BCrypt");
                return false;
            } else {
                return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
            }
        } else {
            this.logger.warn("Empty encoded password");
            return false;
        }
 
    }
}

然后我们只需要在创建配置类的时候创建这个实现类的bean对象就行了.

接下来就是在自定义认证逻辑中进行注入,因为我们在进行自定义认证逻辑中需要对账号和密码进行一个校验,通常我们从数据库中拿出来的密码就已经加密了,但是现在是模拟所以手动加密,然后会将拿到的密码放入到User中进行打包,然后security会对User中的密码进行校验,它会将用户输入的密码和加密过的密码用matchs进行解码然后比较.

实现UserDetailsService类的自定义认证类.

@Service
public class UserDetailServiceImp implements UserDetailsService {

    //密码加密器
    @Autowired
    private PasswordEncoder pw;

    //自定义认证方法
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {


        //从数据库中查找,判断该用户是否存在
        if(!"admin".equals(username)){
            throw new UsernameNotFoundException("该用户不存在,请进行注册");
        }
        //暂时这样写的,实际上应该从数据库中取数据
        String password = pw.encode("123");

        //如果存在则将账号和密码还有相应的权限进行打包,放入到security的User类中,进行认证
        return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));
    }
}

但是这里注意,这里并不进行密码校验,这里会将数据进行取出,然后会将取到的密码,和用户输入加密过后的密码进行令牌匹配,成功的话才能通过.

有了自定义认证类,我们可以将我们的认证方法写在里面,对用户登录进行校验.

5.自定义登录页面

有了自定义方法,我们就可以进行自己的认证方法了,但是页面现在还是使用的是security默认自带的页面,而一般开发我们都是使用自己的页面,所以我们现在要实现使用自己的页面进行认证.

自定义的认证授权器

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //注册一个加密器
    @Bean
    public PasswordEncoder getpw() {

        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        
        //首先,使用表单进行验证
        http.formLogin()
                //当有前端action的值为login的时候视为认证请求,会取调用认证方法
                .loginProcessingUrl("/login")
                //指定默认的认证页面
                .loginPage("/login.html")
                        //认证成功后跳转到默认的页面
                        .successForwardUrl("/toMain")
                                //认证失败后进行跳转到默认的错误页面
                                .failureForwardUrl("/toError");

        //资源访问认证
        http.authorizeRequests()
                //指定的资源不需认证可进行访问
                .antMatchers("/login.html").permitAll()
                .antMatchers("/error.html").permitAll()
                //表示所有请求都需认证
                .anyRequest()
                .authenticated();
        //关闭csrf
        http.csrf().disable();
    }
}

在自定义认证授权器中,定义了自定义的表单认证,同时指定了默认的验证页面和对认证的处理,并且将认证后成功和失败进行跳转,但是不能直接写入资源路径,因为post请求无法识别,只能使用get请求所以要在Controller中进行相应的跳转.

登录页面:




    
    用户登录


用户名:
密码:

需要注意的是,在进行传输的时候action必须要和loginProcessingUrl中的值对应,否则找不到自定义认证方法就会报错,同时用户名和密码传的值name默认必须是username和password否则会出错,但是也可以自定义传入值的名称.

自定义设置账户和密码名称,同时前端传入名也要一样

.usernameParameter("username123")
.passwordParameter("password123");

控制器跳转:

@Controller
public class MyController {

    @RequestMapping("toMain")
    public String toMain(){
        return "redirect:main.html";
    }

    @RequestMapping("toError")
    public String toError(){
        return "redirect:error.html";
    }

}

这样在我们进行验证后就会跳转到相应的页面了.

6.自定义跳转

1)自定义成功登录处理器

在现在前后端分离的情况下,我们一般会跳转到外面的网页,而不是我们的内部资源.

所以我们要对外进行跳转的时候需要使用其他的方法进行跳转.

实现AuthenticationSuccessHandler方法,同时将url放入

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private String url;

    public MyAuthenticationSuccessHandler(String url) {
        this.url = url;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.sendRedirect(url);
    }
}

实现了上面的方法后我们就可以通过secessHanler来实现对指定url的跳转

.successHandler(new MyAuthenticationSuccessHandler("main.html"))

而错误跳转也差不多是这样不过实现类不同.

public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {


    private String url;

    public MyAuthenticationFailureHandler(String url) {
        this.url = url;
    }

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.sendRedirect(url);
    }
}

同时http后的方法是

.failureHandler(new MyAuthenticationFailureHandler("error.html"));

而现在我们的认证授权器如下:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //注册一个加密器
    @Bean
    public PasswordEncoder getpw() {

        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //首先,使用表单进行验证
        http.formLogin()
                //当有前端action的值为login的时候视为认证请求,会取调用认证方法
                .loginProcessingUrl("/login")
                //指定默认的认证页面
                .loginPage("/login.html")
                        //认证成功后跳转到默认的页面
                        .successHandler(new MyAuthenticationSuccessHandler("main.html"))
                                //认证失败后进行跳转到默认的错误页面
                                .failureHandler(new MyAuthenticationFailureHandler("error.html"));

        //资源访问认证
        http.authorizeRequests()
                //指定的资源不需认证可进行访问
                .antMatchers("/login.html").permitAll()
                .antMatchers("/error.html").permitAll()
                //表示所有请求都需认证
                .anyRequest()
                .authenticated();
        //关闭csrf
        http.csrf().disable();
    }
}

7.响应异常信息

在有时候我们访问会出现一系列的问题,从而会返回不同的错误和状态码,但是这对用户来说难以看懂,所以我们需要设置相应的相应异常信息的功能,返回友好的提示或者页面跳转提示用户.

1.实现AccessDeniedHandler接口

/**
 * 设置自定义的403错误响应方式
 */
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {

        //设置响应状态码
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        //设置响应头
        response.setHeader("Content-Type", "application/json;charset=utf-8");

        //拿到响应的输出流
        PrintWriter writer = response.getWriter();

        //设置返回消息内容(json格式)
        writer.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员\"}");

        //将输出内容刷新到内存
        writer.flush();
        writer.close();

    }
}

我们设定专门相应哪个状态码,比如(404)

这个HttpServletResponse是一个枚举类,里面定义了各种状态码

public interface HttpServletResponse extends ServletResponse {
    int SC_CONTINUE = 100;
    int SC_SWITCHING_PROTOCOLS = 101;
    int SC_OK = 200;
    int SC_CREATED = 201;
    int SC_ACCEPTED = 202;
    int SC_NON_AUTHORITATIVE_INFORMATION = 203;
    int SC_NO_CONTENT = 204;
    int SC_RESET_CONTENT = 205;
    int SC_PARTIAL_CONTENT = 206;
    int SC_MULTIPLE_CHOICES = 300;
    int SC_MOVED_PERMANENTLY = 301;
    int SC_MOVED_TEMPORARILY = 302;
    int SC_FOUND = 302;
    int SC_SEE_OTHER = 303;
    int SC_NOT_MODIFIED = 304;
    int SC_USE_PROXY = 305;
    int SC_TEMPORARY_REDIRECT = 307;
    int SC_BAD_REQUEST = 400;
    int SC_UNAUTHORIZED = 401;
    int SC_PAYMENT_REQUIRED = 402;
    int SC_FORBIDDEN = 403;
    int SC_NOT_FOUND = 404;
    int SC_METHOD_NOT_ALLOWED = 405;
    int SC_NOT_ACCEPTABLE = 406;
    int SC_PROXY_AUTHENTICATION_REQUIRED = 407;
    int SC_REQUEST_TIMEOUT = 408;
    int SC_CONFLICT = 409;
    int SC_GONE = 410;
    int SC_LENGTH_REQUIRED = 411;
    int SC_PRECONDITION_FAILED = 412;
    int SC_REQUEST_ENTITY_TOO_LARGE = 413;
    int SC_REQUEST_URI_TOO_LONG = 414;
    int SC_UNSUPPORTED_MEDIA_TYPE = 415;
    int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
    int SC_EXPECTATION_FAILED = 417;
    int SC_INTERNAL_SERVER_ERROR = 500;
    int SC_NOT_IMPLEMENTED = 501;
    int SC_BAD_GATEWAY = 502;
    int SC_SERVICE_UNAVAILABLE = 503;
    int SC_GATEWAY_TIMEOUT = 504;
    int SC_HTTP_VERSION_NOT_SUPPORTED = 505;
}

当我们拿到响应的输出流后可以将我们的提示信息写成json格式添加到里面,返回给前端读取展示,然后将输出刷新到内存中就大功告成了.

接下来我们需要在SecurityConfig中配置

    @Override
    protected void configure(HttpSecurity http) throws Exception {


    @Resource
    private MyAccessDeniedHandler myAccessDeniedHandler;

        //首先,使用表单进行验证
        http.formLogin()
                //当有前端action的值为login的时候视为认证请求,会取调用认证方法
                .loginProcessingUrl("/login")
                //指定默认的认证页面
                .loginPage("/login.html")
                //认证成功后跳转到默认的页面
//                .successHandler(new MyAuthenticationSuccessHandler("index.html"))
                .successForwardUrl("/toIndex")
                //认证失败后进行跳转到默认的错误页面
//                .failureHandler(new MyAuthenticationFailureHandler("error.html"));
                .failureForwardUrl("/toError");

        //资源访问授权认证
        http.authorizeRequests()
                //指定的资源不需认证可进行访问
                .antMatchers("/login.html").access("permitAll")

                //按照权限来判断访问权限
//                .antMatchers("/index.html").hasAnyAuthority("admin", "normal")
//
//                //按照角色来判断访问权限
//                .antMatchers("/index.html").hasAnyRole("admin")
//
//                //按照IP地址来判断访问权限,通常用在后台管理页面,而且localhost和127.0.0.1不一样!!
//                .antMatchers("/index.html").hasIpAddress("127.0.0.1")
                .antMatchers("/error.html").permitAll()
                /**
                 * 使用antMatchers同时也可以对多个静态资源进行放行,通常对js和css文件还有图片资源进行放行
                 * 可以使用*表示匹配任意字符
                 *  **表示任意目录或文件
                 */
                .antMatchers("/js/**", "/css/**").permitAll()
                //表示所有请求都需认证
                .anyRequest()
                .authenticated();

        //关闭防火墙
        http.csrf().disable();

        //自定义异常响应信息
        http.exceptionHandling()
                .accessDeniedHandler(myAccessDeniedHandler);

将我们自定义响应异常的类注入进来,然后在配置中使用我们自定义的异常类进行对应异常的处理.

8.RememberMe功能

在登录的时候通常都会有一个记住密码等等的功能,其实就是用来维持用户登录会话的.当启用后我们会将用户登录后的User信息存入到数据库,当用户在维持世界内进行访问时不再需要进行认证.

1.User持久化

 //该bean创建负责持久化
    @Bean
    public PersistentTokenRepository getPersistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();

        //放入需要的datasource
        jdbcTokenRepository.setDataSource(dataSource);

        //第一次启动自动创建表
        // jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }

这个bean负责对需要使用记住我功能的用户进行持久化,将认证过后的User持久化到数据库中维持会话.

然后将这个类注入到config中,进行使用

同时还需要将我们自定义的认证方法注入进来,用来拿到我们的User,然后将User持久化.

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private MyAccessDeniedHandler myAccessDeniedHandler;

    @Resource
    private UserDetailServiceImp userDetailServiceImp;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private PersistentTokenRepository persistentTokenRepository;


    //注册一个加密器
    @Bean
    public PasswordEncoder getpw() {

        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //首先,使用表单进行验证
        http.formLogin()
                //当有前端action的值为login的时候视为认证请求,会取调用认证方法
                .loginProcessingUrl("/login")
                //指定默认的认证页面
                .loginPage("/login.html")
                //认证成功后跳转到默认的页面
//                .successHandler(new MyAuthenticationSuccessHandler("index.html"))
                .successForwardUrl("/toIndex")
                //认证失败后进行跳转到默认的错误页面
//                .failureHandler(new MyAuthenticationFailureHandler("error.html"));
                .failureForwardUrl("/toError");

        //资源访问授权认证
        http.authorizeRequests()
                //指定的资源不需认证可进行访问
                .antMatchers("/login.html").access("permitAll")

                //按照权限来判断访问权限
//                .antMatchers("/index.html").hasAnyAuthority("admin", "normal")
//
//                //按照角色来判断访问权限
//                .antMatchers("/index.html").hasAnyRole("admin")
//
//                //按照IP地址来判断访问权限,通常用在后台管理页面,而且localhost和127.0.0.1不一样!!
//                .antMatchers("/index.html").hasIpAddress("127.0.0.1")
                .antMatchers("/error.html").permitAll()
                /**
                 * 使用antMatchers同时也可以对多个静态资源进行放行,通常对js和css文件还有图片资源进行放行
                 * 可以使用*表示匹配任意字符
                 *  **表示任意目录或文件
                 */
                .antMatchers("/js/**", "/css/**").permitAll()
                //表示所有请求都需认证
                .anyRequest()
                .authenticated();

        //关闭防火墙
        http.csrf().disable();

        //自定义异常响应信息
        http.exceptionHandling()
                .accessDeniedHandler(myAccessDeniedHandler);

        //记住我功能
        http.rememberMe()
                //设置持久化时间,秒为单位,默认2星期
                .tokenValiditySeconds(60)
                .userDetailsService(userDetailServiceImp)
                .tokenRepository(persistentTokenRepository);

  //退出登录
        http.logout()
                .logoutSuccessUrl("/login.html");

这样用户在登录后指定的时间内不需要再进行认证就可以登录,同时可以指定维持时间,默认两星期,最后就是我们的退出登录,退出后将返回指定的页面,我这里设定的是退出后返回登录页面.

另外就是csrf问题,正常情况可能会打开,但是我们测试是关闭的.

CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。

分析的话可以看看这个贴

利用spring-security解决CSRF问题_I,Frankenstein的博客-CSDN博客_csrf spring从配置到原理,一步步讲解如何利用Spring的security框架来处理scrf问题。https://blog.csdn.net/u013185616/article/details/70446392?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165007837416780271560258%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=165007837416780271560258&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-70446392.142%5Ev9%5Econtrol,157%5Ev4%5Enew_style&utm_term=scurity%E7%9A%84csrf&spm=1018.2226.3001.4187

而通常都是拦截post请求,并且我们提交表单都是使用的post,所以post请求必须带上csrf的token才能进行访问,但是我前端很菜,所以暂时不去探索了,记录一下以后再去填坑.

你可能感兴趣的:(Spring,Security,java,学习,后端)