赞
踩
https://blog.csdn.net/weixin_43461520/article/details/107941983/
github.com/RobodLee/DayDayUP , 目录导航
Spring Security是一个功能强大且高度可定制的 身份验证和 访问控制框架。提供了完善的 认证机制和方法级的 授权功能。是一款非常优秀的权限管理框架。
它的核心是一组过滤器链,不同的功能经由不同的过滤器。
实现在认证服务器上登录,然后获取Token,再访问资源服务器中的资源的功能。
单点登录,即 在一个多应用系统中,只要在其中一个系统上登录之后,不需要在其它系统上登录也可以访问其内容。
例如,京东复杂的微服务架构系统,在下单时系统, 登录完系统返回一个Token(类似于身份证的东西);然后付钱时把Token再传到交易系统中,交易系统验证Token,不需要再登录。
Token是 JWT(JSON Web Token),是一种 用于通信双方之间传递安全信息的简洁的、URL安全的表述性声明规范。一个JWT实际上就是一个字符串,它由三部分组成, 头部、载荷与签名。为了能够直观的看到JWT的结构,我画了一张思维导图:
最终生成的JWT令牌就是下面这样,有三部分,用 . 分隔。
base64UrlEncode(JWT 头)+“.”+base64UrlEncode(载荷)+“.”+HMACSHA256(base64UrlEncode(JWT 头) + “.” + base64UrlEncode(有效载荷),密钥)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
上面示例,JWT在加密解密的时候都用到了同一个密钥 “ robod666 ”,会带来一个弊端,如果密钥泄露,会被用于伪造Token。为了安全,使用非对称加密算法 RSA。
RSA的基本原理有两点:
私钥加密,持有私钥或公钥才可以解密
公钥加密,持有私钥才可解密
创建数据库表SQL:
- -- ----------------------------
- -- Table structure for sys_role
- -- ----------------------------
- DROP TABLE IF EXISTS `sys_role`;
- CREATE TABLE `sys_role` (
- `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
- `ROLE_NAME` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名称',
- `ROLE_DESC` varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色描述',
- PRIMARY KEY (`ID`) USING BTREE
- ) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-
- -- ----------------------------
- -- Records of sys_role
- -- ----------------------------
- INSERT INTO `sys_role` VALUES (1, 'ROLE_USER', '基本角色');
- INSERT INTO `sys_role` VALUES (2, 'ROLE_ADMIN', '超级管理员');
- INSERT INTO `sys_role` VALUES (3, 'ROLE_PRODUCT', '管理产品');
- INSERT INTO `sys_role` VALUES (4, 'ROLE_ORDER', '管理订单');
-
- -- ----------------------------
- -- Table structure for sys_user
- -- ----------------------------
- DROP TABLE IF EXISTS `sys_user`;
- CREATE TABLE `sys_user` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名称',
- `password` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码',
- `status` int(1) NULL DEFAULT 1 COMMENT '1开启0关闭',
- PRIMARY KEY (`id`) USING BTREE
- ) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-
- -- ----------------------------
- -- Records of sys_user
- -- ----------------------------
- INSERT INTO `sys_user` VALUES (1, 'xiaoming', '$2a$10$CYX9OMv0yO8wR8rE19N2fOaXDJondci5uR68k2eQJm50q8ESsDMlC', 1);
- INSERT INTO `sys_user` VALUES (2, 'xiaoma', '$2a$10$CYX9OMv0yO8wR8rE19N2fOaXDJondci5uR68k2eQJm50q8ESsDMlC', 1);
-
- -- ----------------------------
- -- Table structure for sys_user_role
- -- ----------------------------
- DROP TABLE IF EXISTS `sys_user_role`;
- CREATE TABLE `sys_user_role` (
- `UID` int(11) NOT NULL COMMENT '用户编号',
- `RID` int(11) NOT NULL COMMENT '角色编号',
- PRIMARY KEY (`UID`, `RID`) USING BTREE,
- INDEX `FK_Reference_10`(`RID`) USING BTREE,
- CONSTRAINT `FK_Reference_10` FOREIGN KEY (`RID`) REFERENCES `sys_role` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
- CONSTRAINT `FK_Reference_9` FOREIGN KEY (`UID`) REFERENCES `sys_user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
- ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-
- -- ----------------------------
- -- Records of sys_user_role
- -- ----------------------------
- INSERT INTO `sys_user_role` VALUES (1, 1);
- INSERT INTO `sys_user_role` VALUES (2, 1);
- INSERT INTO `sys_user_role` VALUES (1, 3);
- INSERT INTO `sys_user_role` VALUES (2, 4);
-
- SET FOREIGN_KEY_CHECKS = 1;
三张表分别是 用户表,角色表,用户-角色表。密码是加密过的字符串,内容是“ 123 ”;角色用于权限控制。
创建空父工程 SpringSecurityDemo,并在父工程中创建名为 authentication_server的Module作为认证服务。添加必要的依赖。
核心部分代码:
- …………
- # 配置了公钥和私钥的位置
- rsa:
- key:
- pubKeyPath: C:\Users\robod\Desktop\auth_key\id_key_rsa.pub
- priKeyPath: C:\Users\robod\Desktop\auth_key\id_key_rsa
最后的公私钥的标签是自定义的,并不是Spring提供的标签。
工具类:
JsonUtils:提供了json相关的一些操作;
JwtUtils:生成token以及校验token相关方法;
RsaUtils:生成公钥私钥文件,以及从文件中读取公钥私钥。
我们可以将载荷单独封装成一个对象:
- @Data
- public class Payload<T> {
- private String id;
- private T userInfo;
- private Date expiration;
- }
RSA配置类:
- @Data
- @ConfigurationProperties("rsa.key") //指定配置文件的key
- public class RsaKeyProperties {
- private String pubKeyPath;
- private String priKeyPath;
-
- private PublicKey publicKey;
- private PrivateKey privateKey;
-
- @PostConstruct
- public void createKey() throws Exception {
- this.publicKey = RsaUtils.getPublicKey(pubKeyPath);
- this.privateKey = RsaUtils.getPrivateKey(priKeyPath);
- }
- }
启动类中添加注解:
把RSA的配置类放入Spring容器中。
@EnableConfigurationProperties(RsaKeyProperties.class)1
参考:
https://www.jianshu.com/p/a65f883de0c1
首先会进入 UsernamePasswordAuthenticationFilter并且设置权限为null和是否授权为false,然后进入 ProviderManager查找支持 UsernamepasswordAuthenticationToken的 provider并且调用 provider.authenticate(authentication);再然后就是UserDetailsService接口的实现类(也就是自己真正具体的业务了),这时候都检查过了后,就会回调UsernamePasswordAuthenticationFilter并且设置权限(具体业务所查出的权限)和设置授权为true(因为这时候确实所有关卡都检查过了)。
在上面这段话中,提到了一个 UsernamePasswordAuthenticationFilter,一开始进入的就是这个过滤器的 attemptAuthentication()方法,但是这个方法是从form表单中获取用户名密码,需求不符,需要重写这个方法。然后经过一系列的周转,进入到了UserDetailsService.loadUserByUsername()方法中,为了实现自定义业务逻辑,需要去实现这个方法。这个方法返回的是一个UserDetails接口对象,如果想返回自定义对象,可以去实现这个接口。最终用户验证成功之后,调用的是 UsernamePasswordAuthenticationFilter的父类 AbstractAuthenticationProcessingFilter.successfulAuthentication()方法,也需要去重写该方法去实现自定义需求。
实现过程:
- @Data
- public class SysUser implements UserDetails {
- private Integer id;
- private String username;
- private String password;
- private Integer status;
- private List<SysRole> roles = new ArrayList<>(); // SysRole封装了角色信息,和登录无关
-
- // 省略其它UserDetails中的方法
- }
自定义SysUser类实现UserDetails接口,添加自定义字段
- public interface UserService extends UserDetailsService {
- }
- //-----------------------------------------------------------
- @Service("userService")
- public class UserServiceImpl implements UserService {
- …………
- @Override
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- SysUser sysUser = userMapper.findByUsername(username);
- return sysUser;
- }
- }
先定义一个接口 UserService继承 UserDetailsService,用 UserServiceImpl实现 UserService,相当于 UserServiceImpl实现了 UserDetailsService,从而实现 loadUserByUsername()方法:用用户名去数据库中查出对应的SysUser,具体验证流程交给其它的过滤器去实现即可。
自定义过滤器继承UsernamePasswordAuthenticationFilter类,重写**attemptAuthentication()和successfulAuthentication()**方法
- public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter {
-
- private AuthenticationManager authenticationManager;
- private RsaKeyProperties rsaKeyProperties;
-
- public JwtLoginFilter(AuthenticationManager authenticationManager, RsaKeyProperties rsaKeyProperties) {
- this.authenticationManager = authenticationManager;
- this.rsaKeyProperties = rsaKeyProperties;
- }
-
- //这个方法是用来去尝试验证用户的
- @Override
- public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
- try {
- SysUser user = JSONObject.parseObject(request.getInputStream(),SysUser.class);
- return authenticationManager.authenticate(
- new UsernamePasswordAuthenticationToken(
- user.getUsername(),
- user.getPassword())
- );
- } catch (Exception e) {
- try {
- response.setContentType("application/json;charset=utf-8");
- response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
- PrintWriter out = response.getWriter();
- Map<String, Object> map = new HashMap<>();
- map.put("code", HttpServletResponse.SC_UNAUTHORIZED);
- map.put("message", "账号或密码错误!");
- out.write(new ObjectMapper().writeValueAsString(map));
- out.flush();
- out.close();
- } catch (Exception e1) {
- e1.printStackTrace();
- }
- throw new RuntimeException(e);
- }
- }
-
- //成功之后执行的方法
- @Override
- public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
- SysUser sysUser = new SysUser();
- sysUser.setUsername(authResult.getName());
- sysUser.setRoles((List<SysRole>) authResult.getAuthorities());
- String token = JwtUtils.generateTokenExpireInMinutes(sysUser,rsaKeyProperties.getPrivateKey(),24*60);
- response.addHeader("Authorization", "RobodToken " + token); //将Token信息返回给用户
- try {
- //登录成功时,返回json格式进行提示
- response.setContentType("application/json;charset=utf-8");
- response.setStatus(HttpServletResponse.SC_OK);
- PrintWriter out = response.getWriter();
- Map<String, Object> map = new HashMap<String, Object>(4);
- map.put("code", HttpServletResponse.SC_OK);
- map.put("message", "登陆成功!");
- out.write(new ObjectMapper().writeValueAsString(map));
- out.flush();
- out.close();
- } catch (Exception e1) {
- e1.printStackTrace();
- }
- }
- }
Spring Security怎么知道我们要去调用自己的UserService和自定义的过滤器呢?需要配置类
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。