Spring Security (1)

入门,后续更新,供自己学习,记录自己学习,基于Spring boot

码歌视频:https://www.bilibili.com/video/BV1Cz4y1k7rd

Spring Security入门

依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

SecurityConfig

@Configuration
public class SecurityConfig {

    @Bean
    public PasswordEncoder getPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

UserDetailsServiceImpl

package com.kaka.springsecurity.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //1、根据用户名去数据库查询,如果不存在抛异常
        //不使用数据库
        if (!username.equals("admin")){
            throw new UsernameNotFoundException("用户不存在");
        }

        //2、比较密码
        String password = passwordEncoder.encode("123");

		//user包 org.springframework.security.core.userdetails.User
        return new User(username,password, AuthorityUtils
                .commaSeparatedStringToAuthorityList("admin,normal"));
    }
}

此时就简易入门了

自定义登录页面

Spring Security 有自己的登陆界面,那如何自定义呢?

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin().loginPage("/login.html");
    }

    @Bean
    public PasswordEncoder getPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/login" method="post">
    用户名:<input type="text" name="username"/><br>
    密码:<input type="password" name="password"/><br>
    <input type="submit" value="提交"/>
</form>
</body>
</html>

main.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
登录成功!!!
</body>
</html>

这样就可以进到login了,但是有一个问题,所有页面都可以进入了

这个时候就需要后面的关于授权的问题了。这里先入门,不深究,即添加一句语句

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin()
            //自定义登录界面
            .loginPage("/login.html");

    //授权
    http.authorizeRequests()
            //所有请求都必须认证才能访问,必须登录
            .anyRequest().authenticated();
}

这样子写了后,也会出错,重定向次数过多

在这里插入图片描述

放行/login.html

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin()
            //自定义登录界面
            .loginPage("/login.html");

    //授权
    http.authorizeRequests()
            //放行login.html
            .antMatchers("/login.html").permitAll()
            //所有请求都必须认证才能访问,必须登录
            .anyRequest().authenticated();
}

成功进入

在这里插入图片描述

但是这个时候在自己写的lohin界面不可以使用admin 123 进行登录

加上这句话System.out.println(“自定义登录逻辑”);

package com.kaka.springsecurity.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //加上这句话
        System.out.println("自定义登录逻辑");
        //1、根据用户名去数据库查询,如果不存在抛异常
        //不使用数据库
        if (!username.equals("admin")){
            throw new UsernameNotFoundException("用户不存在");
        }

        //2、比较密码
        String password = passwordEncoder.encode("123");


        return new User(username,password, AuthorityUtils
                .commaSeparatedStringToAuthorityList("admin,normal"));
    }
}

并没有走自定义的方法

加上这个方法.loginProcessingUrl("/login"); 注意接口不可以随便写,这样子才会去执行

http.csrf().disable(); 这句话也需要加上

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

    http.formLogin()
        //自定义登录界面
        .loginPage("/login.html")
        //必须和login.html表单提交的接口一样
        .loginProcessingUrl("/login")
        //成功后的跳转地址  *这个是需要post请求的 
        .successForwardUrl("/toMain");

    //授权
    http.authorizeRequests()
        //放行login.html
        .antMatchers("/login.html").permitAll()
        //所有请求都必须认证才能访问,必须登录
        .anyRequest().authenticated();

    //这个需要关闭
    http.csrf().disable();
}
@Controller
public class LoginController {
    @RequestMapping("/toMain")
    public String main(){
            return "redirect:main.html";
            }
    }

细节上的问题就解决的差不多了

登录失败页面跳转

在这里插入图片描述

这个就是登录失败的样子,有登录成功的url ,就会有失败的

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

        http.formLogin()
                //自定义登录界面
                .loginPage("/login.html")
                //必须和login.html表单提交的接口一样
                .loginProcessingUrl("/login")
                //成功后的跳转地址
                .successForwardUrl("/toMain")
                //失败后的跳转地址
                .failureForwardUrl("/toError");

        //授权
        http.authorizeRequests()
                //放行login.html
                .antMatchers("/login.html").permitAll()
                //放行error.html  这个也需要放行
                .antMatchers("/error.html").permitAll()
                //所有请求都必须认证才能访问,必须登录
                .anyRequest().authenticated();

        //这个需要关闭
        http.csrf().disable();
    }
@RequestMapping("/toError")
public String error(){
    return "redirect:error.html";
    }

在这里插入图片描述

设置请求用户名和密码

<!DOCTYPE html><html lang="en"><head>
    <meta charset="UTF-8">
        <title>Title</title>
        </head><body>
        <form action="/login" method="post">
            <!-- method="post" name="username" name="password"  必须这样写,否则会失败 -->
                用户名:<input type="text" name="username"/><br>    
                密码:<input type="password" name="password"/><br>
                    <input type="submit" value="提交"/>
        </form>
</body>
</html>

具体原因请研究源码

解决方案

http.formLogin()
        //设置用户名和密码参数的名字
                .usernameParameter("username")
                .passwordParameter("password")

自定义登录成功处理器

现在大部分都是前后端,都是前端自己去跳

但是.successForwardUrl(“https://www.baidu.com/”)是不可行的

需要自定义MyAuthenticationSuccessHandler

并通过这个.successHandler(new MyAuthenticationSuccessHandler(“https://www.baidu.com/”))配置,前面那个必须注释掉

package com.kaka.springsecurity.handler;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private String url;

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

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        User user = (User) authentication.getPrincipal();
        System.out.println(user.getUsername());
        System.out.println(user.getPassword());
        System.out.println(user.getAuthorities());
        httpServletResponse.sendRedirect(url);
    }
}
http.formLogin()
                //设置用户名和密码参数的名字
                .usernameParameter("username")
                .passwordParameter("password")
                //自定义登录界面
                .loginPage("/login.html")
                //必须和login.html表单提交的接口一样
                .loginProcessingUrl("/login")

//                //成功后的跳转地址
//                .successForwardUrl("/toMain")

                //成功后跳转到百度
                .successHandler(new MyAuthenticationSuccessHandler("https://www.baidu.com/"))
                //失败后的跳转地址
                .failureForwardUrl("/toError");

这样就是跳转登陆界面了,下面看一下基本的信息

自定义登录逻辑adminnull[admin, normal]

这就是输出的基本信息

自定义登录失败处理器

和成功一样,写一个MyAuthenticationFailureHandler

package com.kaka.springsecurity.handler;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {

    private String url;

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


    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        e.printStackTrace();
        httpServletResponse.sendRedirect(url);
    }
}
http.formLogin()
                //设置用户名和密码参数的名字
                .usernameParameter("username")
                .passwordParameter("password")
                //自定义登录界面
                .loginPage("/login.html")
                //必须和login.html表单提交的接口一样
                .loginProcessingUrl("/login")

//                //成功后的跳转地址
//                .successForwardUrl("/toMain")

                //成功后跳转到百度
                //.successHandler(new MyAuthenticationSuccessHandler("https://www.baidu.com/"))
                .successHandler(new MyAuthenticationSuccessHandler("/main.html"))
//                //失败后的跳转地址
//                .failureForwardUrl("/toError");
                .failureHandler(new MyAuthenticationFailureHandler("/error.html"));

登录失败,运行结果

自定义登录逻辑
org.springframework.security.authentication.BadCredentialsException: 用户名或密码错误

现在前后端分离,更常用failureHandler,而不是failureForwardUrl

授权(关于anyRequest)

有一个基本的细节问题,anyRequest必须放在后面

//授权http.authorizeRequests()
//放行login.html        
.antMatchers("/login.html").permitAll()        
//放行error.html        
.antMatchers("/error.html").permitAll()        
//所有请求都必须认证才能访问,必须登录        
.anyRequest().authenticated();

如果放在前面,即错误的写法

//授权
        http.authorizeRequests()
                .anyRequest().authenticated()
                //放行login.html
                .antMatchers("/login.html").permitAll()
                //放行error.html
                .antMatchers("/error.html").permitAll()
                //所有请求都必须认证才能访问,必须登录
                ;

程序直接报错

Caused by: java.lang.IllegalStateException: Can't configure antMatchers after anyRequest

关于antMatchers

  • 这个是最常用的方法(还有两个重载)
public C antMatchers(String... antPatterns)
  • ?:匹配一个字符
  • * :匹配一个或多个字符
  • **:匹配一个或多个目录

打个比方

//表示所有的css,js下的,都放行
.antMatchers("/css/**,/js/**").permitAll();
    
//放行所有的png,不仅仅是在images文件夹下的png
.antMatchers("/**/*.png").permitAll();

关于regexMatchers

基本使用

//授权
http.authorizeRequests()
        //放行login.html
        .antMatchers("/login.html").permitAll()
        //放行error.html
        .antMatchers("/error.html").permitAll()
//        //放行所有的png,不仅仅是在images文件夹下的png
//        .antMatchers("/**/*.png").permitAll()
        //正则表达式
        .regexMatchers(".+[.]png").permitAll()
        //所有请求都必须认证才能访问,必须登录
        .anyRequest().authenticated();

如果完全匹配请求呢?

@GetMapping("/demo")
public String demo(){return "demo";}
.regexMatchers("/demo").permitAll()

这样子是可以直接访问的

.regexMatchers(HttpMethod.POST,"/demo").permitAll()

我们的controller是GetMapping,就也会屏蔽

关于mvcMatchers

基本使用

//mvc匹配.mvcMatchers("/demo").permitAll()

添加前缀

spring.mvc.servlet.path=/kaka
  • 第一种
.regexMatchers("/kaka/demo").permitAll()
  • 第二种
.mvcMatchers("/demo").servletPath("/kaka").permitAll()
  • 第三种
.antMatchers("/kaka/demo").permitAll()

内置权限控制

这是源代码里面写的

static final String permitAll = "permitAll";//表示所匹配的 URL 任何人都允许访问
private static final String denyAll = "denyAll";//表示所匹配的 URL 都不允许被访问
private static final String anonymous = "anonymous";//表示可以匿名访问匹配的 URL
private static final String authenticated = "authenticated";//表示所匹配的 URL 都需要被认证才能访问
private static final String fullyAuthenticated = "fullyAuthenticated";//如果用户不是被 remember me 的,才可以访问
private static final String rememberMe = "rememberMe";//被“remember me”的用户允许访问

用户角色权限说明

画的有点丑

在这里插入图片描述

一般来说,用户角色权限是有五张表的,这篇文章是入门,并且也没有使用数据库,所以就不进行细分了

基于权限判断

将前面代码进行适量调整,并添加main1.html

package com.kaka.springsecurity.config;

import com.kaka.springsecurity.handler.MyAuthenticationFailureHandler;
import com.kaka.springsecurity.handler.MyAuthenticationSuccessHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

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

        http.formLogin()
                //设置用户名和密码参数的名字
                .usernameParameter("username")
                .passwordParameter("password")
                //自定义登录界面
                .loginPage("/login.html")
                //必须和login.html表单提交的接口一样
                .loginProcessingUrl("/login")

                //成功后的跳转地址
                .successForwardUrl("/toMain")

                //成功后跳转到百度
                //.successHandler(new MyAuthenticationSuccessHandler("https://www.baidu.com/"))
//                .successHandler(new MyAuthenticationSuccessHandler("/main.html"))
//                //失败后的跳转地址
                .failureForwardUrl("/toError");
//                .failureHandler(new MyAuthenticationFailureHandler("/error.html"));

        //授权
        http.authorizeRequests()
                //放行login.html
                .antMatchers("/login.html").permitAll()
                //放行error.html
                .antMatchers("/error.html").permitAll()
//                //正则表达式
//                .regexMatchers(HttpMethod.POST,"/demo").permitAll()
//                .regexMatchers("/kaka/demo").permitAll()
                //mvc匹配
//                .mvcMatchers("/demo").servletPath("/kaka").permitAll()
                //所有请求都必须认证才能访问,必须登录
                .anyRequest().authenticated();

        //这个需要关闭
        http.csrf().disable();
    }

    @Bean
    public PasswordEncoder getPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

main.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
登录成功!!!<a href="main1.html">跳转</a>
</body>
</html>

main1.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
admin权限控制
</body>
</html>

前面的UserDetailsServiceImpl是给了两个权限

return new User(username,password, AuthorityUtils
        .commaSeparatedStringToAuthorityList("admin,normal"));

设置权限 严格区分大小写,成功就会进入,失败会报403错误

//单个权限
.antMatchers("/main1.html").hasAuthority("admin")
    
//多个权限   虽然admiN是错误的,但是有admin,依旧可以进入
.antMatchers("/main1.html").hasAnyAuthority("admin","admiN")

基于角色判断

在前面的UserDetailsServiceImpl 加上角色,必须有ROLE_前缀

return new User(username,password, AuthorityUtils 
       .commaSeparatedStringToAuthorityList("admin,normal,ROLE_abc"));

这里不要加前缀,因为他会自动添加,这里也严格区分大小写,这里错误也是403

//单个
.antMatchers("/main1.html").hasRole("abc")
//多个
.antMatchers("/main1.html").hasAnyRole("abc","Abc")

基于IP判断

获取ip地址的方法

request.getRemoteAddr();

有一个问题,localhost获取的是0.0.0.0.0.0.0.1,127.0.0.1获取的是127.0.0.1,还有自己的ip地址,他是不同的

.antMatchers("/main1.html").hasIpAddress("127.0.0.1")

自定义403页面

创建MyAccessDeniedHandler

package com.kaka.springsecurity.handler;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        //响应状态
        httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
        //返回json格式
        httpServletResponse.setHeader("Content-Type","application/json;charset=utf-8");


        PrintWriter writer = httpServletResponse.getWriter();
        writer.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员\"}");
        writer.flush();
        writer.close();
    }
}

因为留了一个之前的.antMatchers("/main1.html").hasRole(“abC”),他会403错误

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

        http.formLogin()
                //设置用户名和密码参数的名字
                .usernameParameter("username")
                .passwordParameter("password")
                //自定义登录界面
                .loginPage("/login.html")
                //必须和login.html表单提交的接口一样
                .loginProcessingUrl("/login")

                //成功后的跳转地址
                .successForwardUrl("/toMain")

                //成功后跳转到百度
                //.successHandler(new MyAuthenticationSuccessHandler("https://www.baidu.com/"))
//                .successHandler(new MyAuthenticationSuccessHandler("/main.html"))
//                //失败后的跳转地址
                .failureForwardUrl("/toError");
//                .failureHandler(new MyAuthenticationFailureHandler("/error.html"));

        //授权
        http.authorizeRequests()
                //放行login.html
                .antMatchers("/login.html").permitAll()
                //放行error.html
                .antMatchers("/error.html").permitAll()
//                .antMatchers("/main1.html").hasAuthority("admin")
                .antMatchers("/main1.html").hasRole("abC")

//                //正则表达式
//                .regexMatchers(HttpMethod.POST,"/demo").permitAll()
//                .regexMatchers("/kaka/demo").permitAll()
                //mvc匹配
//                .mvcMatchers("/demo").servletPath("/kaka").permitAll()
                //所有请求都必须认证才能访问,必须登录
                .anyRequest().authenticated();


        //异常处理
        http.exceptionHandling()
                .accessDeniedHandler(new MyAccessDeniedHandler());


        //这个需要关闭
        http.csrf().disable();
    }

报错

在这里插入图片描述

基于access的访问控制

之前说了很多访问控制,有内置的,角色,权限等等,但是他们都是基于access的

随便复制了几个,可以发现,都是调用的accsee的

public ExpressionUrlAuthorizationConfigurer<H>.ExpressionInterceptUrlRegistry permitAll() {
    return this.access("permitAll");
}

public ExpressionUrlAuthorizationConfigurer<H>.ExpressionInterceptUrlRegistry anonymous() {
    return this.access("anonymous");
}

public ExpressionUrlAuthorizationConfigurer<H>.ExpressionInterceptUrlRegistry rememberMe() {
    return this.access("rememberMe");
}

使用说明

.antMatchers("/login.html").access("permitAll");

.antMatchers("/login.html").access("hasRole('abc')");

自定义access方法

package com.kaka.springsecurity.service;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.util.Collection;

@Service
public class MyServiceImpl  {

    public boolean hasPermission(HttpServletRequest request, Authentication authentication){
        //获取主体
        Object principal = authentication.getPrincipal();

        //判断主体是否属于UserDetails
        if (principal instanceof UserDetails){
            //获取权限
            UserDetails userDetails = (UserDetails) principal;
            Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
            //判断请求的URI是否有权限  所以权限需要加上URI  否则会报500错
            return authorities.contains(new SimpleGrantedAuthority(request.getRequestURI()));
        }
        return false;
    }
}
//所有请求都必须认证才能访问,必须登录
//.anyRequest().authenticated();
//自定义access方法
.anyRequest().access("@myServiceImpl.hasPermission(httpServletRequest,authentication)");
return new User(username,password, AuthorityUtils  
      .commaSeparatedStringToAuthorityList("admin,normal,ROLE_abc,/main.html"));

基于注解的访问控制

@SpringBootApplication
//开启注解配置   注意一定要开启
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class SpringsecurityApplication {

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

}

这些注解可以放在service上,也可以是controller或者是controller的方法上,但是放在方法上好控制,所以一般放在方法上

前面的代码就稍微调整一下 不改会报500错

  • @Secured是专门判断是否具有该角色的,

他会先登录,在进行判断角色,就一开始写了自定义登录逻辑,他就会输出在控制台

@Secured("ROLE_abc")
@RequestMapping("/toMain"
public String main(){
    return "redirect:main.html";
    }
  • @PreAuthorize执行前判断是否有权限,PostAuthorize执行后判断是否有权限,很少用

这两个都可以,配置类不可以有ROLE_,但是他们都严格要求大小写

@PreAuthorize("hasRole('abc')")
//@PreAuthorize("hasRole('ROLE_abc')")
@RequestMapping("/toMain")
public String main(){
    return "redirect:main.html";
    }

RememberMe功能实现

ReRememberMe功能实现依赖Spring-jdbc,所以导入mybatis和数据库依赖

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

配置配置文件

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/security?serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&characterEncoding=UTF8&allowPublicKeyRetrieval=true
    driver-class-name: com.mysql.cj.jdbc.Driver

数据库建表

package com.kaka.springsecurity.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.sql.DataSource;

@Configuration
public class RememberMeConfig {

    @Autowired(required = true)
    private DataSource dataSource;

    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        //设置数据源
        jdbcTokenRepository.setDataSource(dataSource);
        //自动建表,第一次使用开启,后面注释掉,
//        jdbcTokenRepository.setCreateTableOnStartup(true);

        return jdbcTokenRepository;

    }
}
package com.kaka.springsecurity.config;

import com.kaka.springsecurity.handler.MyAccessDeniedHandler;
import com.kaka.springsecurity.handler.MyAuthenticationFailureHandler;
import com.kaka.springsecurity.handler.MyAuthenticationSuccessHandler;
import com.kaka.springsecurity.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private PersistentTokenRepository persistentTokenRepository;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

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

        http.formLogin()
                //设置用户名和密码参数的名字
                .usernameParameter("username")
                .passwordParameter("password")
                //自定义登录界面
                .loginPage("/login.html")
                //必须和login.html表单提交的接口一样
                .loginProcessingUrl("/login")

                //成功后的跳转地址
                .successForwardUrl("/toMain")

                //成功后跳转到百度
                //.successHandler(new MyAuthenticationSuccessHandler("https://www.baidu.com/"))
//                .successHandler(new MyAuthenticationSuccessHandler("/main.html"))
//                //失败后的跳转地址
                .failureForwardUrl("/toError");
//                .failureHandler(new MyAuthenticationFailureHandler("/error.html"));

        //授权
        http.authorizeRequests()
                //放行login.html
                .antMatchers("/login.html").permitAll()
                //放行error.html
                .antMatchers("/error.html").permitAll()
//                .antMatchers("/main1.html").hasAuthority("admin")
//                .antMatchers("/main1.html").hasRole("abC")

//                //正则表达式
//                .regexMatchers(HttpMethod.POST,"/demo").permitAll()
//                .regexMatchers("/kaka/demo").permitAll()
                //mvc匹配
//                .mvcMatchers("/demo").servletPath("/kaka").permitAll()
                //所有请求都必须认证才能访问,必须登录
                .anyRequest().authenticated();
//                //自定义access方法
//                .anyRequest().access("@myServiceImpl.hasPermission(httpServletRequest,authentication)");


        //异常处理
        http.exceptionHandling()
                .accessDeniedHandler(new MyAccessDeniedHandler());

        //记住我
        http.rememberMe()
                .tokenRepository(persistentTokenRepository)
                //超时时间
                .tokenValiditySeconds(60)
                .userDetailsService(userDetailsService);

        //这个需要关闭
        http.csrf().disable();
    }

    @Bean
    public PasswordEncoder getPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

登录一次之后,自动生成了表

在这里插入图片描述

第二次登录,每次都会去增加

在这里插入图片描述

使用thmeleaf

添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>

命名空间

xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"

写一个themeleaf页面

<!DOCTYPE html>
<html lang="en" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
登录账号:<span sec:authentication="name"></span><br>
登录账号:<span sec:authentication="principal.username"></span><br>
凭证:<span sec:authentication="credentials"></span><br>
权限和角色:<span sec:authentication="authorities"></span><br>
客户端地址:<span sec:authentication="details.remoteAddress"></span><br>
sessionId:<span sec:authentication="details.sessionId"></span><br>

<button sec:authorize="hasAnyAuthority('/insert')">新增</button>
<button sec:authorize="hasAnyAuthority('/delete')">删除</button>
<button sec:authorize="hasAnyAuthority('/update')">修改</button>
<button sec:authorize="hasAnyAuthority('/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>
</body>
</html>

根据权限展示

return new User(username,password, AuthorityUtils
        .commaSeparatedStringToAuthorityList("admin,normal,ROLE_abc,/insert,/delete"));

结果
在这里插入图片描述

退出

main.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
登录成功!!!<a href="main1.html">跳转</a><br>
<a href="/logout">退出</a>
</body>
</html>

就可以直接退出了,但是有一些问题

第一个问题http://localhost:8080/login.html?logout 这是它的链接,很明显,我们想要的是重定向会登录界面,而不是传参logout

这样子就可以了

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

第二种

//退出
http.logout()
    	.logoutUrl("/user/logout");
<a href="/user/logout">退出</a>

这样子也行

logout 也可以自定义handler,但是入门级暂时不研究这么深

csrf

  • CSRF全拼为Cross Site Request Forgery,译为跨站请求伪造。
  • CSRF指攻击者盗用了你的身份,以你的名义发送恶意请求。
    • 包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账…
  • 造成的问题:个人隐私泄露以及财产安全。

Spring Security 是默认开启crsf的,之前我们是关闭了的

把login.html 放到templates里面,把配置里面的路径改一下,注释关闭crsf的代码

@GetMapping("/showLogin")
public String login(){
		return "login";
		}

结果

// http://localhost:8080/login{
  	"status": "error",
    "msg": "权限不足,请联系管理员"
    }

修改一下登陆界面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/login" method="post">
    <input type="hidden" name="_csrf" th:value="${_csrf.token}" th:if="${_csrf}"/>
    用户名:<input type="text" name="username"/><br/>xxx
    密码:<input type="password" name="password"/><br/>
    记住我:<input type="checkbox" name="remember-me" value="true"/><br/>
    <input type="submit" value="提交"/>
</form>
</body>
</html>

查看请求头

_csrf: 5fbacf62-6939-40c1-ae38-185b9f63756cusername: adminpassword: 123
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值