SpringSecurity学习笔记

本文详细介绍了Spring Security的基本使用,包括初始化Spring Boot项目、配置数据库、自定义登录页面和处理、密码加密、用户查询、角色和权限控制、IP限制、记住我功能以及退出登录的实现。此外,还提到了注解授权和JWT+OAuth2的相关知识。
摘要由CSDN通过智能技术生成

SpringSecurity的基本使用

重启项目后务必要刷新页面

初始化项目

新建springboot项目,选择springsecurity、web、mysql、mybatis依赖
<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>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
配置数据库(数据库不存在需手动创建)
spring.datasource.password=111111
spring.datasource.username=root
spring.datasource.url=jdbc:mysql://localhost:3306/security?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
启动项目
image-20220102204246245

看到他,说明就没问题

默认用户名:user

默认密码:

image-20220102204454928

当然现在登录成功是404,因为我们什么都没有写。

为什么我们会访问8080就是这个页面?

因为security默认是会要求所有请求认证。在我们没有被认证(即登录)时,任何请求默认都会重定向到这个登录页。

image-20220102205713041
编写一个接口,并访问
@RestController
public class TestController {

    @GetMapping("/hello")
    public String hello() {
        return "hello world!";
    }
}

访问:localhost:8080/hello

image-20220102210108554

依然要先登录,然后才能看到

image-20220102210136092

学习配置security

1.自定义登录

前后端分离的话,可以看看这篇文章:https://blog.csdn.net/qq_42682745/article/details/121326021

我们的配置类需要继承WebSecurityConfigurerAdapter

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //super.configure(http);
        http.formLogin()        //开启表单登录
                .loginPage("/login.html")   //定义登录页面
                .loginProcessingUrl("/mylogin")   //自定义登录请求,form表单中的action就是它
                .usernameParameter("myName")   //自定义用户名参数,默认表单中必须是username
                .passwordParameter("myPass");   //自定义密码参数,默认表单中必须是password


        http.authorizeRequests()    //认证相关
                .antMatchers("/login.html").permitAll()  //放行登录页面
                .antMatchers("/Main.html").denyAll()  //拒绝访问
                .anyRequest().authenticated();   //其余请求都必须认证后才能访问

        
        //从 Spring Security4 开始 CSRF 防护默认开启。默认会拦截请求。进行 CSRF 处理。
        // CSRF 为了保证不是其他第三方网站访问,
        // 要求访问时携带参数名为_csrf 值为 token(token 在服务端产生)的内容,如果token 和	服务端的 token 匹配成功,则正常访问。
        //所以关闭csrf请求才能成功
        http.csrf().disable();
    }
}
  • .antMatchers("/login.html").permitAll() 将matchers中的资源放行,不认证即可访问
  • .anyRequest().authenticated() 除去上面matchers中的资源,其余资源访问都需要认证

随便写一个登录页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/mylogin" method="post">
        <input type="text" name="myName"/>
        <input type="password" name="myPass"/>
        <button type="submit">登录</button>
    </form>
</body>
</html>
image-20220102215139144

ok,

现在我们访问:localhost:8080/hello , 被重定向到我们自定义的登陆页面了

image-20220102215229028

,再输入user和那串密码,

image-20220102220613258

成功

2.登录成功、登录失败处理

image-20220103102700955

successForwardUrl("/success")  //登录成功的post处理接口
.failureForwardUrl("/unsuccess")  //登录失败的post处理接口

失败:

image-20220103102921069

成功:

image-20220103103030140
3.密码加密

PasswordEncoder核心接口,进入PasswordEncoder接口,鼠标双击类名,Ctrl+h,

image-20220103111921820

可以看到,加密方式很多啊。

security官方推荐的是BCryptPasswordEncoder。

下面我们在测试类中用一下:使用BCryptPasswordEncoder加密123456

image-20220103112640939

下面我们在测试类中用一下:使用NoOpPasswordEncoder加密123456

image-20220103113018996

NoOpPasswordEncoder,就是不加密密码,只推荐测试使用;这个已经被标记为过时、不推荐使用。

4.自定义查询用户

UserDetailsService、UserDetails接口就是核心,我们需要了解。

进入UserDetailsService接口,鼠标双击类名,Ctrl+h,

image-20220103110031864

我们就了解,内存中查询用户和数据库查询用户

再看看UserDetails接口,

image-20220103110514052

它就是security的一个用户单位,我们使用它的实现类User即可。注意,是security的User不是你自己写的。

1.内存中

在刚刚的配置文件中,加上这两个方法(一个创建获取用户的bean,一个创建密码加密器bean):

	@Bean
    public UserDetailsService users() {
        //创建内存UserDetailsManager对象
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        //创建用户张三李四
        manager.createUser(User.withUsername("张三").password("123").authorities("p1","p2").build());
        manager.createUser(User.withUsername("李四").password("123").authorities("p3").build());
        return manager;
    }

    @Bean
    public PasswordEncoder encoder() {
        return NoOpPasswordEncoder.getInstance();
    }

image-20220103111205392

张三,密码123,权限p1,p2

李四,密码123,权限p3

ok,现在我们试试用这两个账号登录

image-20220103113618558

登录成功,下面我们再加上权限

http.authorizeRequests()    //认证相关        .antMatchers("/login.html").permitAll()  //放行登录页面        .antMatchers("/unsuccess").permitAll()        //访问hello请求需要有p1权限        .antMatchers("/hello").hasAnyAuthority("p1")        .anyRequest().authenticated();   //其余请求都必须认证后才能访问

先使用,张三(p1、p2权限),登录成功后,浏览器访问localhost:8080/hello

image-20220103115058779

访问成功,因为张三有p1权限。

下面试试李四(p3),登录成功后,浏览器访问localhost:8080/hello

image-20220103115501030

403,无权访问

2.数据库

就是写一个service实现UserDetailsService,loadUserByUsername方法即可。调用mapper去数据库查

@Service
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //查询数据库
        MyUser myUser = userMapper.findByName(username);
        if (myUser == null) {
            return null;
        }
        return new User(myUser.getName(),myUser.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList(myUser.getAuth()));
    }
}

AuthorityUtils.commaSeparatedStringToAuthorityList(“p1,p2”),这个方法会将字符串以逗号分隔为list

这里图方便,我就伪造数据了。。。。用户名是xp,就返回一个User对象,密码123456,权限p1,p2

@Servicepublic class MyUserDetailsService implements UserDetailsService {    @Override    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {        if ("xp".equals(username)) {            return new User(username,"123456", AuthorityUtils.commaSeparatedStringToAuthorityList("p1,p2"));        }        return null;    }}

重启,登录xp

image-20220103125752121

登录成功,并且访问/hello也是可以的

5.角色和权限
权限

权限在前文已经涉及到了

.antMatchers("/hello").hasAnyAuthority("p1","p2") //有其中一个权限即可

相信大家很容易理解,有什么权限才能干什么事

角色

其实和权限差不多

image-20220103131651421

角色,必须以ROLE_ 开头,后面跟角色名

@Servicepublic class MyUserDetailsService implements UserDetailsService {    @Override    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {        if ("xp".equals(username)) {            return new User(username,"123456", AuthorityUtils.commaSeparatedStringToAuthorityList("p1,p2,ROLE_admin,ROLE_test"));        }        return null;    }}

我们再改一下security的配置,加上:.antMatchers("/test").hasAnyRole(“admin”,“test”)

在配置类中就只需要写角色名,不能加ROLE_

http.authorizeRequests()    //认证相关
        .antMatchers("/login.html").permitAll()  //放行登录页面
        .antMatchers("/unsuccess").permitAll()
        .antMatchers("/Main.html").denyAll()  //拒绝访问
        //访问hello请求需要有p1权限
        .antMatchers("/hello").hasAnyAuthority("p1","p2")
        .antMatchers("/test").hasAnyRole("admin","test")
        .anyRequest().authenticated();   //其余请求都必须认证后才能访问

访问/test接口需要有角色admin或者test,有其中一个角色即可访问。登录xp,访问test

image-20220103132028345

IP控制
//指定ip才能访问
.antMatchers("/test").hasIpAddress("192.168.1.4")

localhost的ip是 0:0:0:0:0:0:0:1

127.0.0.1 的ip就是本身

自定义403权限不足提示

https://www.bilibili.com/video/BV1gb4y1b7XE?p=20

自己写一个类,实现AccessDeniedHandler接口

image-20220103154338258

将MyAccessDeniedHandler注入到配置类,然后

image-20220103154438444

6.注解
@Secured

作用于类、方法

验证是否有具有指定角色,有才能访问

角色必须是以ROLE_ 开头的


现在,将配置类的角色相关的注释掉

1.启动类,增加@EnableGlobalMethodSecurity(securedEnabled = true)

image-20220103135732952

2.在test接口上加上

@Secured("ROLE_admin,ROLE_test")@GetMapping("/test")public String test() {    return "您可以访问/test接口";}

重启,登录访问test,成功。

现在将test接口的角色,改一下

@Secured("ROLE_xx")@GetMapping("/test")public String test() {    return "您可以访问/test接口";}

无权访问

值得一提的是,@Secured(“ROLE_admin,ROLE_test”)和配置中的hasAnyRole(“admin”,“test”)。虽然都能写多个角色。hasAnyRole(“admin”,“test”)只需要其中一个权限就能访问。但是@Secured(“ROLE_admin,ROLE_test”)必须全部具备才能访问

@PreAuthorize()和@PostAuthorize()

它两也作用于方法或者类。

判断有不有权限

@PreAuthorize(),执行方法前判断

@PostAuthorize(),执行方法后判断

很明显,我们既然做权限控制,自然是使用@PreAuthorize()更多,它两的用法一样。

1.启动类注解:

@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)

2.修改test接口

//@Secured("ROLE_admin,ROLE_test")
@PreAuthorize("hasAnyAuthority('p1','p2')")
@GetMapping("/test")
public String test() {
    return "您可以访问/test接口";
}

3.将配置类中权限控制相关的注释调

启动–登录–访问test–成功

修改test接口:

@PreAuthorize("hasAnyAuthority('p8')")
@GetMapping("/test")
public String test() {
    return "您可以访问/test接口";
}
image-20220103142045366

@PreAuthorize里面的写法和配置文件一样,接口都是有提示的

7.记住我

我们需要在配置类中加上一些东西:

1.注入依赖:

@Autowired
private MyUserDetailsService myUserDetailsService;
@Autowired
private DataSource dataSource; //配置好mysql参数后,springboot会自动装配一个DataSource
@Autowired
private PersistentTokenRepository persistentTokenRepository;

2.写bean方法:

@Bean
//返回持久化token到数据库的bean
public PersistentTokenRepository persistentTokenRepository() {
    JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
    //配置数据源
    jdbcTokenRepository.setDataSource(dataSource);
    //自动创建所需要的表,第二次启动需要注释掉不然报错
    jdbcTokenRepository.setCreateTableOnStartup(true);
    return jdbcTokenRepository;
}

3.加上配置

http.rememberMe()  //开启记住我功能
        .rememberMeParameter("remember")  //自定义记住我参数名,remember-me
        .userDetailsService(myUserDetailsService) //指定查询用户的service对象
        .tokenRepository(persistentTokenRepository);  //指定token持久化对象

4.修改test接口,权限改回来

@PreAuthorize("hasAnyAuthority('p1')")
@GetMapping("/test")
public String test() {
    return "您可以访问/test接口";
}

5.登陆页面修改

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/mylogin" method="post">
        <input type="text" name="myName"/><br/>
        <input type="password" name="myPass"/></br/>
        记住我:<input type="checkbox" name="remember" checked="checked">
        <button type="submit">登录</button>
    </form>

</body>
</html>

重启,访问:

image-20220103151342391

第二次启动记得将建表的代码注释掉

现在,我们关闭浏览器。直接访问:localhost:8080/test,看会不会让我们再登录。

直接访问到了,不会跳转到登录页面

image-20220103152127699

保存到数据库的token有效期默认为两周,

我们也可以自己设置

.tokenValiditySeconds(10086)  //设置token有效时间,单位秒

完整的配置类代码:

import javax.sql.DataSource;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private MyUserDetailsService myUserDetailsService;
    @Autowired
    private DataSource dataSource;


    @Bean
    public PasswordEncoder encoder() {
        return NoOpPasswordEncoder.getInstance();
    }

    @Bean
    //返回持久化token到数据库的bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        //自动创建所需要的表,第二次启动需要注释掉不然报错
        //jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //super.configure(http);
        http.formLogin()        //开启表单登录
                .loginPage("/login.html")   //定义登录页面
                .loginProcessingUrl("/mylogin")   //自定义登录请求,form表单中的action就是它
                .usernameParameter("myName")   //自定义用户名参数,默认表单中必须是username
                .passwordParameter("myPass")   //自定义密码参数,默认表单中必须是password
                .successForwardUrl("/success")
                .failureForwardUrl("/unsuccess");

        http.authorizeRequests()    //认证相关
                .antMatchers("/login.html").permitAll()  //放行登录页面
                .antMatchers("/unsuccess").permitAll()
                .antMatchers("/Main.html").denyAll()  //拒绝访问
                //访问hello请求需要有p1权限
                .antMatchers("/hello").hasAnyAuthority("p1","p2")
                //.antMatchers("/test").hasAnyRole("admin","test")
                .anyRequest().authenticated();   //其余请求都必须认证后才能访问

        http.rememberMe()  //开启记住我功能
                .tokenValiditySeconds(10086)  //设置token有效时间,单位秒
                .rememberMeParameter("remember")  //自定义记住我参数名,remember-me
                .userDetailsService(myUserDetailsService) //指定查询用户的service
                .tokenRepository(persistentTokenRepository());  //token持久化


        //从 Spring Security4 开始 CSRF 防护默认开启。默认会拦截请求。进行 CSRF 处理。
        // CSRF 为了保证不是其他第三方网站访问,
        // 要求访问时携带参数名为_csrf 值为 token(token 在服务端产生)的内容,如果token 和服务端的 token 匹配成功,则正常访问。
        //所以关闭csrf请求才能成功
        http.csrf().disable();
    }
}
8.退出登录

退出就很简单了,我们找个页面,写个a标签都可以,security默认的退出就是"/logout"

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

如果你想自定义退出路径也可以:

image-20220103211233329

即可。

jwt+oauth2待更新

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

为了我的架构师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值