项目之通过Spring Security获取当前登录的用户的信息(6)

您所在的位置:网站首页 springsecurity获取用户信息 项目之通过Spring Security获取当前登录的用户的信息(6)

项目之通过Spring Security获取当前登录的用户的信息(6)

2024-07-10 06:57| 来源: 网络整理| 查看: 265

20. 使用控制器转发注册页面

将用户注册的register.html文件移动到templates文件夹下。

在SystemController中添加:

代码语言:javascript复制@GetMapping("/register.html") public String register() { return "register"; }

在SecurityConfig中,将注册相关的"/register.html"和"/portal/user/student/register"这2个URL添加到白名单中。

21. 处理用户的权限21.1. 补全:学生注册时分配角色

在“学生注册”的业务中,应该及时获取新插入的用户数据的id,并将该用户id和角色id(学生角色的id固定为2)插入到user_role数据表中,以记录新注册的学生的角色。

先在UserServiceImpl中添加:

代码语言:javascript复制@Autowired private UserRoleMapper userRoleMapper;

然后,在原有的“学生注册”的业务最后补充:

代码语言:javascript复制// 向“用户角色表”中插入数据,为当前学生账号分配角色 UserRole userRole = new UserRole(); userRole.setUserId(user.getId()); userRole.setRoleId(2); // 学生角色的id固定为2,具体可参见user_role数据表 rows = userRoleMapper.insert(userRole); // 判断返回值(受影响的行数)是否不为1 if (rows != 1) { // 是:受影响的行数不是1,则插入用户角色数据失败,抛出InsertException throw new InsertException("注册失败!服务器忙,请稍后再次尝试!"); }

完成后,需要在“学生注册”的业务方法之前添加@Transactional注解,以启用事务。

关于事务,它是数据库提供的一种机制,它可以保证一系列的写操作(包括插入、删除、修改)要么全部成功,要么全部失败!

假设存在数据:

账号

余额

苍松

1000

国斌

8000

如果要实现“国斌向苍松转账5000元”,需要执行的数据操作有:

代码语言:javascript复制UPDATE 账户表 SET 余额=余额-5000 WHERE 账号='国斌'; UPDATE 账户表 SET 余额=余额+5000 WHERE 账号='苍松';

万一,在执行过程中,因为某些不可控的因素,导致前一条SQL语句成功的执行了,但是后一条SQL语句却无法执行,就会导致数据安全问题。在这种情况下,就需要使用事务,如果2条SQL语句都执行成功,则圆满完成,如果任何1条执行出错,只要保证全部是失败的(哪怕之前已经执行成功了某些SQL语句,也将失败),数据安全也不会受到影响!

基于Spring JDBC的事务处理,只需要在业务方法之前添加@Transactional注解即可。其处理机制大致是:

代码语言:javascript复制try { 开启事务:BEGIN 执行若干个数据访问操作(增、删、改、查) 提交事务(保存数据):COMMIT } catch (RuntimeException e) { 回滚事务:ROLLBACK }

所以,为了保证事务机制的有效执行,必须:

如果某个业务中涉及2次或以上的写操作(例如2次INSERT操作,或1次INSERT加1次DELETE等),都必须在业务方法之前添加@Transactional注解,以启用事务;每次调用了持久层的写操作后,都必须及时获取返回的“受影响的行数”,并且判断返回值是否与预期值相符合,如果不符合,必须抛出RuntimeException或其子孙类异常的对象!

在开发项目时,之所以需要将业务异常继承自RuntimeException,是因为:

便于编写代码,避免使用异常时需要使用严格的语法声明抛出或捕获,因为RuntimeException及其子孙类异常都不强制要求try...catch或throw/throws,并且,业务层抛出异常后,在控制器层也是全部再次抛出,交由统一处理异常的机制进行处理的;保证事务机制的正常使用。

另外,@Transactional注解还可以添加在业务类的声明之前,会使得当前类中所有的方法都是基于事务机制来运行的,但是,一般并没有这个必要性,所以,不推荐这样使用!

还应该了解:事务的ACID特性,事务的隔离,事务的传播。

21.2. 处理登录时获取权限

以上注册过程中添加了“分配角色”,而各角色是对应某些权限的,所以,“分配角色”的过程就是“分配权限”的过程!在用户登录时,应该读取用户的权限,以完成Spring Security在验证过程中的授权,以保证后续在进行某些访问时,能给出正确的判断,使得某些用户可以执行某些操作,而另一些用户可能因为没有权限而不能执行这些操作!

首先,需要实现“根据用户id查询该用户的权限”的功能,需要执行的SQL语句大致是:

代码语言:javascript复制SELECT DISTINCT permission.* FROM permission LEFT JOIN role_permission ON permission.id=role_permission.permission_id LEFT JOIN role ON role_permission.role_id=role.id LEFT JOIN user_role ON role.id=user_role.role_id LEFT JOIN user ON user_role.user_id=user.id WHERE user.id=1;

在处理权限数据的持久层PermissionMapper接口中添加抽象方法:

代码语言:javascript复制/** * 查询某用户的权限 * @param userId 用户的id * @return 该用户的权限的列表 */ List selectByUserId(Integer userId);

然后,在PermissionMapper.xml中配置以上抽象方法对应的SQL语句:

代码语言:javascript复制 SELECT DISTINCT permission.id, permission.name, permission.description FROM permission LEFT JOIN role_permission ON permission.id=role_permission.permission_id LEFT JOIN role ON role_permission.role_id=role.id LEFT JOIN user_role ON role.id=user_role.role_id LEFT JOIN user ON user_role.user_id=user.id WHERE user.id=#{userId}

完成后,在测试位置创建PermissionMapperTests测试类,编写并执行单元测试:

代码语言:javascript复制package cn.tedu.straw.portal.mapper; @SpringBootTest @Slf4j public class PermissionMapperTests { @Autowired PermissionMapper mapper; @Test void selectByUserId() { Integer userId = 1; List permissions = mapper.selectByUserId(userId); log.debug("permissions count={}", permissions.size()); for (Permission permission : permissions) { log.debug("permission > {}", permission); } } }

接下来,在处理登录的业务中,也就是在UserServiceImpl中先添加:

代码语言:javascript复制@Autowired private PermissionMapper permissionMapper;

并在login()方法中补充:

代码语言:javascript复制// 权限字符串数组 List permissions = permissionMapper.selectByUserId(user.getId()); String[] authorities = new String[permissions.size()]; for (int i = 0; i < permissions.size(); i++) { authorities[i] = permissions.get(i).getName(); } // 组织“用户详情”对象 UserDetails userDetails = org.springframework.security.core.userdetails.User .builder() .username(user.getUsername()) .password(user.getPassword()) .authorities(authorities) .disabled(user.getEnabled() == 0) .accountLocked(user.getLocked() == 1) .build();

由于修改了注册的业务(刚刚添加了“为学生账号分配角色”),原本的测试数据可能会不可用,为了便于后续的测试使用,应该先将原有数据全部清空:

代码语言:javascript复制TRUNCATE user;

并通过注册业务或注册页面再次注册一些新的账号。

同时,还应该将一些数据标识为老师:

代码语言:javascript复制UPDATE user SET type=1 WHERE id IN (1, 2, 3);

在用户角色分配表中,清空原有数据,将一部分账号的角色改为管理员、老师:

代码语言:javascript复制-- 清空用户角色分配表 TRUNCATE user_role; -- 将某些用户分配为管理员、老师、学生 INSERT INTO user_role (user_id, role_id) VALUES (1, 1), (1, 2), (1, 3); -- 将某些用户分配为老师 INSERT INTO user_role (user_id, role_id) VALUES (2, 3), (3, 3); -- 将某些用户分配为学生 INSERT INTO user_role (user_id, role_id) VALUES (4, 2), (5, 2), (6, 2);22. 通过Spring Security获取当前登录的用户的信息

当用户成功登录后,需要获取用户的信息才可以执行后续的操作,例如获取某用户的权限、获取某用户的问题列表、获取某用户的个人信息等等。

Spring Security提供了简便的获取当前登录用户信息的做法,在控制器的处理请求的方法中,添加Authentication类型的参数,或添加Principal类型的参数,均可获得当前登录用户的信息,例如:

代码语言:javascript复制// http://localhost:8080/test/user/current/authentication @GetMapping("/user/current/authentication") public Authentication getAuthentication(Authentication authentication) { return authentication; } // http://localhost:8080/test/user/current/principal @GetMapping("/user/current/principal") public Principal getPrincipal(Principal principal) { return principal; }

以上2种做法输出的结果是完全相同的,因为Authentication是继承自Principal的,当Spring MVC框架尝试注入参数值时,注入的是同一个对象!

以上做法输出的内容比较多,还可以使用以下做法来获取用户信息:

代码语言:javascript复制// http://localhost:8080/test/user/current/details @GetMapping("/user/current/details") public UserDetails getUserDetails(@AuthenticationPrincipal UserDetails userDetails) { return userDetails; }23. 扩展UserDetails

通过以上注入@AuthenticationPricipal UserDetails userDetails后可以获取用户的信息,但是,对象中封装的信息可能不足以满足编程需求,例如没有用户的id或其它的某些属性!如果需要存在这些属性,就需要自定义类,扩展自UserDetails!

在cn.tedu.straw.portal.security包下创建UserInfo类,继承自User类,并在这个类中声明所需的自定义属性:

代码语言:javascript复制package cn.tedu.straw.portal.security; @Setter @Getter @ToString public class UserInfo extends User { private Integer id; private String nickname; private Integer gender; private Integer type; public UserInfo(String username, String password, Collection


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3