Spring Boot整合Spring Security

GitHub:SpringBootDemo
Gitee:SpringBootDemo
微信公众号:在这里插入图片描述

Spring Security是针对Spring项目的安全框架,也是Spring Boot底层安全模块的默认技术选型,仅需引入spring-boot-starter-security模块,进行少量配置,即可实现强大的Web安全控制。

Spring Security的两个主要目标是认证授权(访问控制)

官方文档:https://docs.spring.io/spring-security/site/docs/5.2.0.RELEASE/reference/htmlsingle/

0 开发环境

  • JDK:1.8
  • Spring Boot:2.7.18

Spring Boot 版本升级为2.7.18,专栏中其他Spring Boot相关环境同步升级

1 引入依赖

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

2 测试

2.1 新建Controller

@RestController
@RequestMapping("user")
public class UserController {

    @GetMapping(value = "query")
    public String query() {
        return "用户查询成功";
    }
}

2.2 测试

启动服务,浏览器访问 127.0.0.1:8090/user/query,页面自动跳转到授权登录页

在这里插入图片描述

默认用户名为user,控制台上会打印默认密码,默认密码每次启动服务都会刷新

在这里插入图片描述

登录成功后,就可以正常访问了

在这里插入图片描述

3 自定义密码

该部分会使用到Spring Security的几个关键类,如下:

  • WebSecurityConfigurerAdapter 自定义Security策略
  • AuthenticationManagerBuilder 自定义认证策略
  • @EnableWebSecurity 开启WebSecurity模式

3.1 通过application.yml 配置

spring:
  security:
    user:
      name: admin
      password: 123456

3.2 自定义配置类配置

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //密码加密
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        //配置用户名、密码,该配置方式下,用户名和密码保存在内存中
        auth.inMemoryAuthentication()
                //密码加密方式
                .passwordEncoder(new BCryptPasswordEncoder())
                .withUser("admin").password(passwordEncoder.encode("123456")).roles("admin");
    }
}

3.3 自定义实现类配置

3.3.1 编写UserDetailsService实现类

这里我们就直接固定写死用户名和密码,实际生产中可以从数据库中获取

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        //设置角色,角色的概念后续介绍
        List<GrantedAuthority> roles = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");

        return new User("admin", new BCryptPasswordEncoder().encode("123456"), roles);
    }
}

3.3.2 编写配置类

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //使用UserDetailsServiceImpl 查询用户名、密码
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }
}

分别测试,都通过

4 用户认证和授权/基于角色和权限的访问控制

实际生产中,需要根据用户角色的权限来控制可访问的页面、可执行的操作等

4.1 新建4个页面

level-1.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 style="color: red">这是用户等级1可访问的页面</h1>
</body>
</html>

level-2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 style="color: green">这是用户等级2可访问的页面</h1>
</body>
</html>

level-3.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 style="color: blue">这是用户等级3可访问的页面</h1>
</body>
</html>

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<a href="level-1.html">等级1</a><br><br>
<a href="level-2.html">等级2</a><br><br>
<a href="level-3.html">等级3</a>
</body>
</html>

4.2 修改配置类

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //请求授权的规则
        //开启认证
        http.authorizeRequests()
                //首页所有人可访问
                .antMatchers("/").permitAll()
                //功能页对应角色或权限才能访问
                //hasRole 为角色授权,表示用户拥有指定角色
                //hasAuthority 为权限授权,表示用户拥有指定权限
                .antMatchers("/level-1.html").hasRole("level1")
                .antMatchers("/level-2.html").hasRole("level2")
                .antMatchers("/level-3.html").hasAuthority("level3");

        //开启登录,无权限时进入登录页面
        http.formLogin();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //密码加密
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        //配置用户名、密码,该配置方式下,用户名和密码保存在内存中
        auth.inMemoryAuthentication()
                //密码加密方式
                .passwordEncoder(new BCryptPasswordEncoder())
                .withUser("admin").password(passwordEncoder.encode("123456")).roles("admin")
                .and().withUser("admin1").password(passwordEncoder.encode("123456")).roles("level1")
                .and().withUser("admin2").password(passwordEncoder.encode("123456")).roles("level2")
                .and().withUser("admin3").password(passwordEncoder.encode("123456")).authorities("level3")
                .and().withUser("admin0").password(passwordEncoder.encode("123456")).authorities("ROLE_level1", "ROLE_level2", "level3");
    }
}

**hasRole()hasAuthority()**用法是类似的,只不过hasRole()方法会给自定义的角色名前加上 ROLE_ 前缀

在这里插入图片描述

因此在自定义用户时,如果使用**authorities()给用户设置角色时,需要自行添加上ROLE_**前缀。

roles()authorities()设置的角色或权限,最终都存放在authorities参数中,且这两个方法会互相覆盖彼此的值。

在这里插入图片描述

4.3 测试

浏览器访问

在这里插入图片描述

依次点击等级1、等级2、等级3,均自动跳转到授权登录页面,登录对应权限的用户后,可成功访问。

其中,登录admin用户,无法访问任何页面,登录admin0,可访问所有页面

在这里插入图片描述

登录权限不匹配的用户,拒绝访问

在这里插入图片描述

4.4 使用UserDetailsService类实现

UserDetailsServiceImpl

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        List<GrantedAuthority> roles;
        if ("admin1".equals(s)) {
            roles = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_level1");
        } else if ("admin2".equals(s)) {
            roles = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_level2");
        } else if ("admin3".equals(s)) {
            roles = AuthorityUtils.commaSeparatedStringToAuthorityList("level3");
        } else if ("admin0".equals(s)) {
            roles = AuthorityUtils.createAuthorityList("ROLE_level1", "ROLE_level2", "level3");
        } else {
            roles = AuthorityUtils.createAuthorityList("admin");
        }

        return new User(s, new BCryptPasswordEncoder().encode("123456"), roles);
    }
}

SecurityConfig

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //请求授权的规则
        //开启认证
        http.authorizeRequests()
                //首页所有人可访问
                .antMatchers("/").permitAll()
                //功能页对应角色或权限才能访问
                //hasRole 为角色授权,表示用户拥有指定角色
                //hasAuthority 为权限授权,表示用户拥有指定权限
                .antMatchers("/level-1.html").hasRole("level1")
                .antMatchers("/level-2.html").hasRole("level2")
                .antMatchers("/level-3.html").hasAuthority("level3");

        //开启登录,无权限时进入登录页面
        http.formLogin();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //使用UserDetailsServiceImpl 查询用户名、密码
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }
}

浏览器访问,测试,通过

5 常用注解

可控制用户认证访问接口

5.1 @Secured()

校验用户具有某个角色,才可访问接口

需在启动类开启注解

@EnableGlobalMethodSecurity(securedEnabled = true)

然后在接口方法上配置注解

@RestController
@RequestMapping("user")
public class UserController {

    @GetMapping(value = "query")
    @Secured("ROLE_level1")
    public String query() {
        return "用户查询成功";
    }

    @GetMapping(value = "update")
    @Secured({"ROLE_level1", "ROLE_level2"})
    public String update() {
        return "用户更新成功";
    }
}

5.2 @PreAuthorize()

在进入方法前校验用户具有某个权限或角色

需在启动类开启注解

@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)

然后在接口方法上配置注解

@RestController
@RequestMapping("user")
public class UserController {

    @GetMapping(value = "delete")
    @PreAuthorize("hasAnyAuthority('ROLE_level1','level3')")
    public String delete() {
        return "用户删除成功";
    }
}

5.3 @PostAuthorize()

在进入方法后校验用户具有某个权限或角色

需在启动类开启注解

@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)

然后在接口方法上配置注解

@RestController
@RequestMapping("user")
public class UserController {

    @GetMapping(value = "delete")
    @PostAuthorize("hasAnyAuthority('ROLE_level1','level3')")
    public String delete() {
        return "用户删除成功";
    }
}

5.4 @PostFilter()

校验权限后对数据进行过滤,只返回满足条件的数据

新建实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserModel {

    private String username;
    private String password;
}

然后在方法上加上注解

@RestController
@RequestMapping("user")
public class UserController {

    @GetMapping(value = "queryList")
    @PreAuthorize("hasAnyAuthority('ROLE_level1','level3')")
    @PostFilter("filterObject.username == 'test'")
    public List<UserModel> queryList() {
        List<UserModel> userList = new ArrayList<>();
        userList.add(new UserModel("test", "qwerty"));
        userList.add(new UserModel("test2", "asdfgh"));
        userList.add(new UserModel("test3", "zxcvbn"));

        return userList;
    }
}

测试,权限验证通过后

在这里插入图片描述

5.5 @PreFilter()

校验权限后对数据进行过滤,只有满足条件的数据才能传入接口方法中

@RestController
@RequestMapping("user")
public class UserController {

    @PostMapping(value = "queryUser")
    @PreAuthorize("hasAnyAuthority('ROLE_level1','level3')")
    @PreFilter("filterObject.username == 'test2'")
    public List<UserModel> queryUser(@RequestBody List<UserModel> userModels) {
        return userModels;
    }
}

测试,权限验证通过后

在这里插入图片描述

6 记住我

配置类中开启记住我

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //请求授权的规则
        //开启认证
        http.authorizeRequests()
                //首页所有人可访问
                .antMatchers("/").permitAll()
                //功能页对应角色或权限才能访问
                //hasRole 为角色授权,表示用户拥有指定角色
                //hasAuthority 为权限授权,表示用户拥有指定权限
                .antMatchers("/level-1.html").hasRole("level1")
                .antMatchers("/level-2.html").hasRole("level2")
                .antMatchers("/level-3.html").hasAuthority("level3");

        //开启登录,无权限时进入登录页面
        http.formLogin();

        //记住我
        http.rememberMe();
    }

启动服务,访问页面,登录页面增加了记住我选择框

在这里插入图片描述

登录成功后,cookie中已保存用户信息,默认时间为2周

在这里插入图片描述

7 注销

7.1 配置类中开启注销

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //请求授权的规则
        //开启认证
        http.authorizeRequests()
                //首页所有人可访问
                .antMatchers("/").permitAll()
                //功能页对应角色或权限才能访问
                //hasRole 为角色授权,表示用户拥有指定角色
                //hasAuthority 为权限授权,表示用户拥有指定权限
                .antMatchers("/level-1.html").hasRole("level1")
                .antMatchers("/level-2.html").hasRole("level2")
                .antMatchers("/level-3.html").hasAuthority("level3");

        //开启登录,无权限时进入登录页面
        http.formLogin();

        //记住我
        http.rememberMe();

        //开启注销,注销成功后回首页
        http.logout().logoutSuccessUrl("/");
    }

7.2 level-* 页面增加注销按钮

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 style="color: red">这是用户等级1可访问的页面</h1>
<br><br>
<a href="/logout">注销</a>
</body>
</html>

其他两个页面做相同修改

启动服务,登录成功后点击注销按钮,注销成功,返回首页,访问页面需再次登录

8 自定义登录页

8.1 新建登录页login.html

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

    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f5f5f5;
        }

        .container {
            width: 300px;
            margin: auto;
            padding: 40px;
            border: 1px solid #ccc;
            background-color: white;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
        }

        h2 {
            text-align: center;
        }

        label {
            display: block;
            margin-bottom: 10px;
        }

        input[type="text"],
        input[type="password"] {
            width: 100%;
            padding: 6px;
            border: 1px solid #ccc;
            outline: none;
        }

        button {
            width: 100%;
            padding: 10px;
            color: white;
            background-color: #4CAF50;
            cursor: pointer;
            border: none;
            outline: none;
        }

        button:hover {
            opacity: 0.9;
        }
    </style>
</head>
<body>
<div class="container">
    <h2>登录</h2>
    <form action="/login" method="post">
        <label for="username">用户名</label>
        <input type="text" id="username" name="username"><br><br>

        <label for="password">密码</label>
        <input type="password" id="password" name="password"><br><br>

        记住我:<input type="checkbox" name="remember-me" title="记住我"><br><br>

        <button type="submit">登 录</button>
    </form>
</div>
</body>
</html>

8.2 配置类中开启自定义登录页

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //请求授权的规则
        //开启认证
        http.authorizeRequests()
                //首页所有人可访问
                .antMatchers("/").permitAll()
                //功能页对应角色或权限才能访问
                //hasRole 为角色授权,表示用户拥有指定角色
                //hasAuthority 为权限授权,表示用户拥有指定权限
                .antMatchers("/level-1.html").hasRole("level1")
                .antMatchers("/level-2.html").hasRole("level2")
                .antMatchers("/level-3.html").hasAuthority("level3");

        //开启登录,无权限时进入登录页面
        //自定义登录页
        http.formLogin().loginPage("/login.html").loginProcessingUrl("/login");
        //关闭csrf防护
        http.csrf().disable();

        //记住我
        http.rememberMe();

        //开启注销,注销成功后回首页
        http.logout().logoutSuccessUrl("/");
    }

这里,自定义登录,默认用户参数是username,默认密码参数是password,默认记住我参数是remember-me,如果需要自定义登录表单的参数,做如下修改

        //开启登录,无权限时进入登录页面
        http.formLogin().loginPage("/login.html").loginProcessingUrl("/login")
                .usernameParameter("username")
                .passwordParameter("password");
        //关闭csrf防护
        http.csrf().disable();

        //记住我
        http.rememberMe().rememberMeParameter("remember-me");

启动服务,访问地址,跳转到自定义登录页

在这里插入图片描述

9 自定义403页面

9.1 新建403.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
权限不足,无法访问
</body>
</html>

9.2 配置类中开启自定义403页面

    @Override
    protected void configure(HttpSecurity http) throws Exception {
		//...其他代码...

        //自定义403页面
        http.exceptionHandling().accessDeniedPage("/403.html");
    }

启动服务,浏览器访问,登录无权限用户后提示

在这里插入图片描述

至此,Spring Boot整合Spring Security实现用户认证和授权基本用法已讲解完毕,且测试通过。

  • 26
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值