文章目录

  • 一、Spring Security 简介
  • 二、整合 Spring Security
    • 1、创建项目,导入依赖
    • 2、创建数据库,搭建环境
    • 3、配置数据库
    • 4、创建实体类
    • 5、实现 UserMapper
    • 6、实现 UserService
    • 7、实现 Controller
    • 8、配置 Spring Security
    • 9、拦截登录认证测试

一、Spring Security 简介

通常,一个正式的项目都会具有安全方面的需求。显然,手动去实现权限管理等功能是较为麻烦的。因此,我们们往往需要借助一些安全框架来实现,而 Spring Security 就是常见的安全框架之一。具体而言,Spring Security 可以帮助我们自动完成如下功能:

  • 请求拦截
  • 登录认证
  • 角色授权
  • 账户注销
  • 自动登录
  • 其他功能 …

其中,作为一个安全框架,最核心的自然还是认证以及授权功能,我们只需提供相关的信息,剩下的大部分工作都可以自动实现,这也是其强大的地方。同时,作为 Spring 家族的一部分,Spring Boot 也能够很好地将其整合,只需要导入一个依赖并添加一个配置类即可。

以下将结合数据库,在 Spring Boot 项目中整合 Spring Security,并实现基本的登录认证、权限管理等功能。

二、整合 Spring Security

1、创建项目,导入依赖

创建一个 Spring Boot 项目(默认添加 web 依赖)
项目结构如下:

然后在 pom.xml 中导入如下依赖:

  • spring-boot-starter-security:整合 Spring Security
  • themeleaf:视图跳转需要用到
  • jdbc, mysql, mybatis:连接数据库
  • lombok:通过注解生成实体类的 getter/setter,提高效率
     <dependency><groupId>org.thymeleaf</groupId><artifactId>thymeleaf-spring5</artifactId></dependency><dependency><groupId>org.thymeleaf.extras</groupId><artifactId>thymeleaf-extras-java8time</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.1</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency>

2、创建数据库,搭建环境

一共有如下 3 张表:

  • user:用户表
  • role:角色(权限)表
  • user_role:多对多关联表

    创建数据库 springboot-data,建表的 SQL 如下:
DROP TABLE IF EXISTS `role`;CREATE TABLE `role` (`role_id` int(10) NOT NULL AUTO_INCREMENT,`role_name` varchar(30) NOT NULL,`role_detail` varchar(30) NOT NULL,PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='角色表';insert  into `role`(`role_id`,`role_name`,`role_detail`) values (1,'ROLE_sadmin','超级管理员'),(2,'ROLE_admin','管理员'),(3,'ROLE_user','普通用户');DROP TABLE IF EXISTS `user`;CREATE TABLE `user` (`id` int(10) NOT NULL AUTO_INCREMENT,`name` varchar(20) NOT NULL,`pwd` varchar(30) NOT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='用户表';insert  into `user`(`id`,`name`,`pwd`) values (1,'root','123456'),(2,'admin','123456'),(3,'study','123456');DROP TABLE IF EXISTS `user_role`;CREATE TABLE `user_role` (`id` int(10) NOT NULL AUTO_INCREMENT,`user_id` int(10) NOT NULL,`role_id` int(10) NOT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='用户-角色表';insert  into `user_role`(`id`,`user_id`,`role_id`) values (1,1,1),(2,1,2),(3,2,2),(4,3,3);

3、配置数据库

application.yaml 中配置数据库连接相关参数:

spring:datasource:username: rootpassword: 123456url: jdbc:mysql://localhost:3306/springboot-data?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8driver-class-name: com.mysql.cj.jdbc.Drivermybatis:type-aliases-package: com.study.pojomapper-locations: classpath:mapper/*.xml

4、创建实体类

创建 Role 实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role {private Integer role_id;private String role_name;private String role_detail;
}

通过 lombok 提供的 @Data注解 自动生成 getter/setter;通过 @AllArgsConstructor 注解与 @NoArgsConstructor 注解分别生成有参构造以及无参构造
创建 User 实体类,实现 UserDetails 接口,重写其中的方法,在该类中用一个 List 集合存放该用户所有的角色
由于密码认证工作都是由 Spring Security 自动完成的,因此我们在这里只是简单地返回用户的各种信息即可,如 getPassword() 返回用户的密码,如果用户登录失败,Spring Security 会自动抛出相关异常。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements UserDetails {private Integer id;private String name;private String pwd;private Integer enabled;private Integer locked;private List<Role> roles;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {List<SimpleGrantedAuthority> authorities = new ArrayList<>();for (Role role : roles) {authorities.add(new SimpleGrantedAuthority(role.getRole_name()));}return authorities;}@Overridepublic String getPassword() {return pwd;}@Overridepublic String getUsername() {return name;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return locked == 0;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return enabled == 1;}}
  • isAccountNonExpired:账户是否未到期
  • isAccountNonLocked:账户是否未被锁定
  • isCredentialsNonExpired:账户密码是否未到期
  • isEnabled:账户是否可用

5、实现 UserMapper

UserMapper 接口中定义如下两个方法:

  • queryUserByName :根据用户名查询用户
  • queryRolesByUid:根据用户 id 查询角色
@Repository
@Mapper
public interface UserMapper {User queryUserByName(@Param("name") String name);List<Role> queryRolesByUid(@Param("id") int id);}

对应的 mapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.study.mapper.UserMapper"><select id="queryUserByName" resultType="user">select * from user where name = #{name};</select><select id="queryRolesByUid" resultType="role">select *from role r, user_role urwhere ur.role_id = r.role_id and ur.user_id = #{id};</select></mapper>

6、实现 UserService

创建 UserService,实现 UserDetailsService 接口,Spring Security 在进行登录认证时,会执行其中的 loadUserByUsername 方法,传入的参数为用户表单中输入用户名,我们通过该用户名在数据库中查找对应的 user,如果找不到,则抛出异常,如果用户存在,则根据其 id 找出所有角色,并将其赋值给当前的 user:

@Service
public class UserService implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {User user = userMapper.queryUserByName(name);if (user == null) {throw new UsernameNotFoundException("账户不存在!");}user.setRoles(userMapper.queryRolesByUid(user.getId()));return user;}}

7、实现 Controller

@Controller
public class RouterController {@RequestMapping({"/", "/index"})public String index() {return "index";}@RequestMapping("/toLogin")public String toLogin() {return "login";}@GetMapping("/sadmin/search")@ResponseBodypublic String showSadminData() {return "授权通过,拥有超级管理员权限!";}@GetMapping("/admin/search")@ResponseBodypublic String showAdminData() {return "授权通过,拥有管理员权限!";}@GetMapping("/user/search")@ResponseBodypublic String showUserData() {return "授权通过,拥有普通用户权限!";}}
  • //index 跳转到首页,后续需在 Spring Security 配置类中放行
  • /toLogin 请求跳转到自定义的登录页面 login.html,后续需在 Spring Security 配置类中指定其为 loginPage
  • 其他三个请求 分别表示三种角色权限可以访问的请求

8、配置 Spring Security

自定义一个类继承自 WebSecurityConfigurerAdapter,同时添加 Configuration 注解:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {}
  • 重写其中的 configure(AuthenticationManagerBuilder auth) 方法,将 userService 传入其中
  • 重写 configure(WebSecurity web) 方法,其中将静态资源路径放行,当然也可通过 HttpSecurity .authorizeRequests().antMatchers("xxx").permitAll() 实现
  • passwordEncoder 中返回 NoOpPasswordEncoder.getInstance(),即不采取加密
    @Autowiredprivate UserService userService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService);}@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/css/**", "/js/**", "/index.html","/img/**", "/fonts/**", "/favicon.ico");}@BeanPasswordEncoder passwordEncoder() {return NoOpPasswordEncoder.getInstance();}

接下来就是 Spring Security 的核心配置了:

  • .antMatchers("/").permitAll() 表示将 "/" 请求放行,所有人都能访问
  • antMatchers("/sadmin/**").hasRole("sadmin")表示拦截以 /sadmin 开头的所有请求,只有当用户具备 sadmin 角色时才放行,下面的两行依次类推
  • .anyRequest().authenticated() 表示所有的请求都需登录认证后才可访问
  • formLogin() 表示开启表单登录;loginPage 为自定义跳转的登录页面(如不设置,则进入到 Spring Securtity 提供的默认登录界面)
  • loginProcessingUrl 表示处理登录的请求名称,表单的登录请求对应此 url
  • usernameParameter("name").passwordParameter("pwd") 为登录表单中 input 的自定义的 name 名称,如不设置,默认为 username, password
  • successHandler 添加登录成功的回调函数,failureHandler 添加登录失败的回调函数,一般在其中返回一些提示信息
  • logout 开启注销功能,即当我们访问 /logout 请求时,自动完成注销操作
  • rememberMe 开启记住我功能,当勾选记住我登录时,会创建一个 CookierememberMeParameter 为表单控件中单选框的 name 名称,类似于上面的 usernameParameter
    @Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/").permitAll().antMatchers("/sadmin/**").hasRole("sadmin").antMatchers("/admin/**").hasRole("admin").antMatchers("/user/**").hasRole("user").anyRequest().authenticated().and().formLogin().loginPage("/toLogin").loginProcessingUrl("/login").permitAll().usernameParameter("name").passwordParameter("pwd").successHandler(successHandler()).failureHandler(failureHandler()).and().csrf().disable().logout().and().rememberMe().rememberMeParameter("remember");}

以下是登录成功的回调函数,这里将用户信息以 JSON 形式返回:

    public AuthenticationSuccessHandler successHandler() {return new AuthenticationSuccessHandler() {@Overridepublic void onAuthenticationSuccess(HttpServletRequest req,HttpServletResponse resp,Authentication authentication)throws IOException, ServletException {Object principal = authentication.getPrincipal();resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();resp.setStatus(200);Map<String, Object> map = new HashMap<>();map.put("status", 200);map.put("msg", principal);ObjectMapper objectMapper = new ObjectMapper();out.write(objectMapper.writeValueAsString(map));out.flush();out.close();}};}

以下是登录失败的回调函数,根据 Spring Security 抛出的异常信息,将错误信息以 JSON 形式返回:

    public AuthenticationFailureHandler failureHandler() {return new AuthenticationFailureHandler() {@Overridepublic void onAuthenticationFailure(HttpServletRequest req,HttpServletResponse resp,AuthenticationException e)throws IOException, ServletException {resp.setContentType("application/json;charset=utf-8");PrintWriter out = resp.getWriter();resp.setStatus(401);Map<String, Object> map = new HashMap<>();map.put("status", 401);if (e instanceof LockedException) {map.put("msg", "账户被锁定!");} else if (e instanceof BadCredentialsException) {map.put("msg", "用户名或密码错误!");} else if (e instanceof DisabledException) {map.put("msg", "账户被禁用!");} else if (e instanceof AccountExpiredException) {map.put("msg", "账户已过期!");} else if (e instanceof CredentialsExpiredException) {map.put("msg", "密码已过期!");} else {map.put("msg", "登录失败!");}ObjectMapper objectMapper = new ObjectMapper();out.write(objectMapper.writeValueAsString(map));out.flush();out.close();}};}

9、拦截登录认证测试

数据库的初始数据如下:

注意:role_name 前要加上前缀 ROLE_

运行项目,浏览器地址栏中输入 localhost:8080,可以成功访问到首页,因为我们在前面放行了 / 请求

点击下方的 3 个超链接,将被 Spring Security 拦截,自动跳转到我们自定义的登录界面

在接口测试工具中,测试登录功能
密码错误,执行失败回调,返回的 JSON 中可以看到对应的错误信息

用户名和密码都正确,执行成功回调,返回该角色的所有信息

接着在,单选框中勾选 记住我 ,然后提交登录,可以看到多了一个名为 remember-me 的 cookie

由于 root 具有 sadmin 以及 admin 权限,所以访问以 /sadmin/admin 开头的请求都能够成功

root 不具备 user 权限,故访问以 /user 开头的请求将别拦截,403 错误表示认证不通过

其他两个用户的结果也可以类似的测试出来,这里就不再细说了。
以上就是 Spring Security 中一些常用功能的使用了,我们对一些常用的安全管理框架还是要有所了解与掌握的。

Spring Security | 轻松搞定认证授权~相关推荐

  1. Spring Security使用数据库登录认证授权

    一.搭建项目环境 1.创建 RBAC五张表 RBAC,即基于角色的权限访问控制(Role-Based Access Control),就是用户通过角色与权限进行关联. 在这种模型中,用户与角色之间,角 ...

  2. SpringCloud整合spring security+ oauth2+Redis实现认证授权

    文章目录 设置通用父工程依赖 构建eureka注册中心 构建认证授权服务 配置文件设置 Security配置类 授权服务配置类 登录实现 测试验证 设置通用父工程依赖 在微服务构建中,我们一般用一个父 ...

  3. Spring Boot实践 | 利用Spring Security快速搞定权限控制

    目录 开始之前 快速开始 使用内存签名服务 使用数据库签名服务 使用自定义签名服务 限制请求 强制使用HTTPS 防止跨站点伪造请求 用户认证功能 在java web工程中,一般使用Servlet过滤 ...

  4. 一个 Spring 注解轻松搞定循环重试功能!

    一.循环重试场景 发送消息失败. 调用远程服务失败. 争抢锁失败. 这些错误可能是因为网络波动造成的,等待过后重处理就能成功.通常来说,会用try/catch,while循环之类的语法来进行重处理,但 ...

  5. Spring Security 实战干货:OAuth2授权回调的核心认证流程

    1. 前言 我们在上一篇 Spring Security 实战干货:OAuth2 授权回调的处理机制 对 OAuth2 服务端调用客户端回调的流程进行了图解, 今天我们来深入了解 OAuth2 在回调 ...

  6. Spring Security关于用户身份认证与授权

    Spring Security是用于解决认证与授权的框架. 创建spring项目,添加依赖 <!-- Spring Boot Security:处理认证与授权 --><depende ...

  7. 从零开始超详细的Spring Security OAuth2.0实现分布式系统授权(注册中心+网关+认证授权服务(JWT令牌验证)+资源调用服务)

    文章目录 一.OAuth2.0 1.介绍 2.例子 3.执行流程 二.Spring Cloud Security OAuth2 1.环境介绍 2.认证流程 三.整合分布式项目 1.技术方案 2.项目结 ...

  8. iframe带了token不显示_不就是登录吗,能有多复杂?sa-token带你轻松搞定多地登陆、单地登录、同端互斥登录...

    前言 在java的世界里,有很多优秀的权限认证框架,如Apache Shiro.Spring Security 等等.这些框架背景强大,历史悠久,其生态也比较齐全. 但同时这些框架也并非十分完美,在前 ...

  9. ASP.NET2.0轻松搞定统计图表【月儿原创】

    ASP.NET2.0轻松搞定统计图表 作者:清清月儿 主页:http://blog.csdn.net/21aspnet/           时间:2007.3.27 本文讲述如何绘制条形图,折线图, ...

最新文章

  1. JVM参数-XX:+HeapDumpOnOutOfMemoryError
  2. 【工作分解法】IT人,你的工作“轻松”么?
  3. SAP CRM IBASE头部字段valid from和valid to的填充逻辑
  4. “视网膜”重装来袭 AI技术为视频业务场景赋能
  5. oracle使用游标批量删除数据,oracle 游标批量处理数据
  6. python delete_rows,Python:如何刪除以特定字符結尾的行?
  7. [导入]DotText源码阅读(2)-工程、数据库表结构
  8. 第十五回(二):文会内战平分秋色 树下阔论使坏心焦【林大帅作品】
  9. DisplayLink 安装错误
  10. FP-Growth算法
  11. 让系统“飞”起来 读懂电脑虚拟内存常遇问题
  12. js函数中变量声明提前
  13. 冰河木马实验(V8.4)
  14. shiro教程3(加密)
  15. ffmpeg命令:pcm和wav转换
  16. 使用Photoshop制作圣诞海报
  17. goldwave教程分享:用GoldWave进行音量调节
  18. matlab的omega0是什么,ABO设定中,A具体是怎么标记O的?
  19. 在CheckiO上熟悉编程
  20. python验证码识别cnn_用CNN识别验证码的实用教程

热门文章

  1. 运动会加油稿计算机学院150字,运动会加油稿150字
  2. 计算机软件选修课选什么好,互联网行业,软件工程专业学什么?
  3. 使用逐浪CMS做网站如何引用Markdown编辑器
  4. 第一周礼拜四 神藉着祂的众仆人行事(上)
  5. IE浏览器如何实现断点续传
  6. 76----平面二次曲线的分类: 消去二次交叉项
  7. Qt的gui编程是,点击一次button出现两次action
  8. WSL关闭与windows的互交互(解决PATH等环境变量问题
  9. Linux(四)——CROND和磁盘分区与挂载
  10. php 导出excel (html),php两种导出excel的方法