6.基于传统的Cookie和Session实现用户的认证授权
6.1 概述
RBAC(Role-Based Access Control,基于角色的访问控制)权限按钮控制是一种细粒度的权限管理方式,它允许系统管理员根据用户的角色来精确控制用户界面中功能按钮的可见性和可用性。在RBAC模型中,权限不仅限于访问某个页面或模块,还可以细化到页面上每个操作按钮的权限。
前几天以及前段时间一直在思考这个问题,昨天与好友一起讨论了下这个问题,并结合自己在网上查到的资料得以实现。下面给出整个项目代码的重点部分,完整代码如下:
以下代码借鉴了这篇博客,有兴趣可以查看【认证与授权】2、基于session的认证方式 - 黑米面包派 - 博客园 (cnblogs.com)
6.2 依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.ku.test</groupId> <artifactId>test-project</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.7.RELEASE</version> <relativePath /> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> </dependencies> </project> pom.xml
6.3 配置
server.port=8080
6.4 主启动类
package com.ku.test; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class TestApplication { public static void main(String[] args) { SpringApplication.run(TestApplication.class, args); } }
6.5 用户信息
为了方便测试这里只是列出了简单的用户名、密码、权限集合等属性
package com.ku.test.entity; import java.util.Set; public class User { private Integer id; private String username; private String password; private Set<String> auth; public User(Integer id, String username, String password, Set<String> auth) { this.id = id; this.username = username; this.password = password; this.auth = auth; } public Integer getId() { return id; } public String getUsername() { return username; } public String getPassword() { return password; } public Set<String> getAuth() { return auth; } }
6.6 service层
为了方便测试,并没有使用Mapper从数据库取数据,而是使用Map存储用户信息
package com.ku.test.service; import com.ku.test.entity.User; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @Service public class UserService { Map<String, User>userMap = new HashMap<String, User>(){{ put("xiaoku", new User(1, "xiaoku", "123456", new HashSet<String>(){{ add("/user/test"); add("/user/test1"); }} )); put("spring", new User(2, "spring", "123456", new HashSet<String>(){{ add("/user/test1"); add("/user/test2"); add("/user/test3"); add("/user/test4"); add("/user/test5"); }} )); }}; public User getUserByName(String username){ return userMap.get(username); } public User auth(String username, String password){ User user = userMap.get(username); if (user == null) throw new RuntimeException("用户不存在!"); if (!password.equals(user.getPassword())) throw new RuntimeException("密码错误!"); return user; } }
6.7 Controller层
package com.ku.test.controller; import com.ku.test.common.constant.CommonConstants; import com.ku.test.entity.User; import com.ku.test.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @GetMapping("/test") public String test(){ return "test"; } @GetMapping("/test1") public String test1(){ //如果请求参数中没有包含HttpServletRequest,则不会被拦截器拦截 return "test1"; } @PostMapping("/login") public String login(HttpServletRequest request){ String username = request.getParameter("username"); String password = request.getParameter("password"); if (request == null || StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) return "用户名或密码为空"; User user = userService.auth(username, password); request.getSession().setAttribute(CommonConstants.USER_SESSION_KET, user); return user.getUsername()+"登录成功!"; } }
6.8 配置类
注册认证授权拦截器,并设置拦截路径和非拦截路径
package com.ku.test.config; import com.ku.test.interceptor.AuthorizeInterceptor; import com.ku.test.interceptor.LoginInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { //1.添加认证拦截器,并设置拦截路径以及排除路径 registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns("/user/login"); //2.添加授权拦截器,并设置拦截路径以及排除路径 registry.addInterceptor(new AuthorizeInterceptor()).addPathPatterns("/**").excludePathPatterns("/user/login"); } }
6.9 认证授权拦截器
当用户登录成功后会将用户的信息存储在Session中,认证拦截器实现原理是判断后续请求中Session中是否通过Key获取到的用户是否为空来判断用户是否认证过
package com.ku.test.interceptor; import com.ku.test.common.constant.CommonConstants; import com.ku.test.entity.User; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { User user = (User)request.getSession().getAttribute(CommonConstants.USER_SESSION_KET); //认证:在拦截器中拦截所有Controller方法的请求,但是排除登录和注册两个请求 // 当用户注册登录后,如果用户为空则说明未登录, if (user != null) return true; response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setContentType("application/json;charset=UTF-8"); //以字符串返回未登录信息给客户端 String errorMessage = new String("请先登录!"); //写入Response返回给客户端 response.getWriter().write(errorMessage); return false; } } 认证拦截器
在用户登录后会将用户信息存储在Session中,授权拦截器的实现原理是获取请求的URL与用户信息中的权限集合进行比较,当用户的权限集合包含该路径时,不拦截
package com.ku.test.interceptor; import com.ku.test.common.constant.CommonConstants; import com.ku.test.entity.User; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Iterator; import java.util.Set; public class AuthorizeInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //获取该用户的请求路径 String path = request.getServletPath(); User user = (User)request.getSession().getAttribute(CommonConstants.USER_SESSION_KET); Set<String> auth = user.getAuth(); boolean successAuthorize = false; Iterator<String> iterator = auth.iterator(); while (iterator.hasNext()){ if (auth.contains(path)){ successAuthorize = true; break; } break; } if (successAuthorize) return true; response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); response.setContentType("application/json;charset=UTF-8"); //以字符串返回未登录信息给客户端 String errorMessage = new String("权限不足!"); //写入Response返回给客户端 response.getWriter().write(errorMessage); return false; } }
6.10 结果
(1)拥有某方法访问权限的用户
(2)无某方法访问权限的用户