系列目录

前言

上篇文章SpringSecurity整合了一半,这次把另一半整完,所以本篇的序号接着上一篇。

七、自定义用户信息

前面我们登录都是用的指定的用户名和密码或者是springsecurity默认的用户名和打印出来的密码。我们要想连接上自定义数据库只需要实现一个自定义的UserDetailsService。

我们新建一个JwtUserDto继承UserDetails并实现它的方法

@Data
@AllArgsConstructor
public class JwtUserDto implements UserDetails {
//用户数据
private MyUser myUser;
//用户权限的集合
@JsonIgnore
private List<GrantedAuthority> authorities; public List<String> getRoles() {
return authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
}
//加密后的密码
@Override
public String getPassword() {
return myUser.getPassword();
}
//用户名
@Override
public String getUsername() {
return myUser.getUserName();
}
//是否过期
@Override
public boolean isAccountNonExpired() {
return true;
}
//是否锁定
@Override
public boolean isAccountNonLocked() {
return true;
}
//凭证是否过期
@Override
public boolean isCredentialsNonExpired() {
return true;
}
//是否可用
@Override
public boolean isEnabled() {
return myUser.getStatus() == 1 ? true : false;
}
}

自定义一个UserDetailsServiceImpl实现UserDetailsService

@Service
@Slf4j
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserService userService;
@Autowired
private MenuDao menuDao;
@Override
public JwtUserDto loadUserByUsername(String userName) throws UsernameNotFoundException {
MyUser user = userService.getUser(userName);//根据用户名获取用户
if (user == null ){
throw new UsernameNotFoundException("用户名不存在");//这个异常一定要抛
}else if (user.getStatus().equals(MyUser.Status.LOCKED)) {
throw new LockedException("用户被锁定,请联系管理员");
}
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
List<MenuIndexDto> list = menuDao.listByUserId(user.getId());
List<String> collect = list.stream().map(MenuIndexDto::getPermission).collect(Collectors.toList());
for (String authority : collect){
if (!("").equals(authority) & authority !=null){
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(authority);
grantedAuthorities.add(grantedAuthority);
}
}//将用户所拥有的权限加入GrantedAuthority集合中
JwtUserDto loginUser =new JwtUserDto(user,grantedAuthorities);
return loginUser;
} }

这里在获取权限的时候遇到了个小小的坑,就是mybatis数据里的空值和null,在你从未对这个数据修改时,它就是null。如果修改了又删除掉了,它就会是空值。

meudao中的listByUserId方法

 @Select("SELECT DISTINCT sp.id,sp.parent_id,sp.name,sp.icon,sp.url,sp.type,sp.permission  " +
"FROM my_role_user sru " +
"INNER JOIN my_role_menu srp ON srp.role_id = sru.role_id " +
"LEFT JOIN my_menu sp ON srp.menu_id = sp.id " +
"WHERE " +
"sru.user_id = #{userId}")
@Result(property = "title",column = "name")
@Result(property = "href",column = "url")
List<MenuIndexDto> listByUserId(@Param("userId")Integer userId);

八、加密

老话题来聊一聊,加密的重要性。

2011年国内某开发者社区(可不就是csdn吗)被攻击数据库,600多万明文存储的用户账号被公开,大量用户隐私泄露。

这是个老梗了,几乎每篇说加密重要性的博文中,csdn的事就要被拿出来遛一遛。

那么为什么密码加密怎么重要??因为在你的数据库被攻击泄露了数据时,如果你的密码也被黑客掌握,那么即使你修复好了数据库泄露的问题,黑客手上仍然还有着用户的密码(总不能要求所有用户修改密码吧)

所以我们需要在系统开发之初就尽量的避免这种问题。

那么说了这么多,怎么来加密呢?

其实在SpringSecurity种已经内置了密码的加密机制,只需要实现一个PasswordEncoder接口即可。

来看一下源码

public interface PasswordEncoder {
String encode(CharSequence var1); boolean matches(CharSequence var1, String var2); default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
  • encode():把参数按照特定的解析规则进行解析。
  • matches()验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹配,则返回 true;如果不匹配,则返回 false。
  • upgradeEncoding():如果解析的密码能够再次进行解析且达到更安全的结果则返回 true,否则返回 false。默认返回 false。

第一个参数表示需要被解析的密码。第二个参数表示存储的密码。

Spring Security 还内置了几种常用的 PasswordEncoder 接口,官方推荐使用的是BCryptPasswordEncoder

。我们来配置一下。在SpringConfig种添加如下代码。

	@Autowired
private UserDetailsService userDetailsService;
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}//自定义userDetailsService加密

是不是十分简单,我们再重启项目,这时候控制台就不再打印密码,现在需要输入数据库中的用户名密码才能登录。

九、获取用户信息

之前我们在绘制菜单时,把用户的id给写死了。现在我们要从SpringSecurity中来获取用户信息。

有两种方法获取已登录用户的信息,一种是从session中拿,另一种就是SpringSecurity提供的方法。这里选择后一种方法。

我们可以通过以下方法来获取登录后用户的信息(其余还有获取登录ip等方法,不多介绍)

SecurityContextHolder.getContext().getAuthentication().getPrincipal()

我们转换下类型

JwtUserDto jwtUserDto = (JwtUserDto)SecurityContextHolder.getContext().getAuthentication().getPrincipal();

打印一下jwtUserDto,看到我们确实拿到了用户的信息

那么我们改写下通过用户id获取菜单这个方法

 	@GetMapping(value = "/index")
@ResponseBody
@ApiOperation(value = "通过用户id获取菜单")
public List<MenuIndexDto> getMenu() {
JwtUserDto jwtUserDto = (JwtUserDto)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Integer userId = jwtUserDto.getMyUser().getId();
return menuService.getMenu(userId);
}

在将前端写死的userId删除。现在我们已经能根据登录用户的不同来自动绘制菜单了。

拥有admin权限的用户

普通权限的用户

十、授权

我们目前只是绘制出了不同权限用户能操作的界面,但是还没有真正的进行权限控制。

之前在七中,我们已经将每个用户所拥有的权限集合放入了GrantedAuthority集合中

在之前打印的用户信息中可以看到 authorities中就是该用户所拥有的权限

SpringSecurity会自动帮我们进行权限控制。而我们要做的就是在需要进行权限控制的方法上添加上权限标识即可。

例如:用户管理的权限标识是user:list

我们只需要在相关的接口上加上@PreAuthorize("hasAnyAuthority('user:list')")即可

	@GetMapping("/index")
@PreAuthorize("hasAnyAuthority('user:list')")
public String index(){
return "system/user/user";
}
@GetMapping
@ResponseBody
@ApiOperation(value = "用户列表")
@PreAuthorize("hasAnyAuthority('user:list')")
public Result<MyUser> userList(PageTableRequest pageTableRequest, UserQueryDto userQueryDto){
pageTableRequest.countOffset();
return userService.getAllUsersByPage(pageTableRequest.getOffset(),pageTableRequest.getLimit(),userQueryDto);
}

现在我们登录普通用户来操作相关接口,发现报错

控制台打印

修改所有接口,在需要权限控制的接口上添加注解

十一、自定义异常处理

虽说现在功能已经实现了,用户虽说不能访问没有权限的功能了,但是异常没有处理。如果点击,如果前端也没有做错误的拦截的话,用户会看到一串的报错信息,这很不友好,并且也会对服务器造成压力。

我们只需要在之前创建的全局异常处理类中捕获上图的异常即可。

	@ExceptionHandler(AccessDeniedException.class)
public Result handleAuthorizationException(AccessDeniedException e)
{
log.error(e.getMessage());
return Result.error().code(ResultCode.FORBIDDEN).message("没有权限,请联系管理员授权");
}

重启项目,在前端书写相应规则,就会十分友好

十二、自定义退出登录

其实SpringSecurity默认注册了一个/logout路由,通过这个路由可以注销登录状态,包括Session和remember-me等等。

我们可以直接在SpringSecurityConfig的configure中定义相应规则,类似formlogin。也可以自定义一个LogoutHadnler,具体可以看这篇文章

至此SpringSecurity的一些常用功能已经实现,下一节我们整合jwt实现无状态登录

本系列giteegithub中同步更新

SpringSecurity权限管理系统实战—五、整合SpringSecurity(下)的更多相关文章

  1. SpringSecurity权限管理系统实战—六、SpringSecurity整合jwt

    目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ...

  2. SpringSecurity权限管理系统实战—四、整合SpringSecurity(上)

    目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ...

  3. SpringSecurity权限管理系统实战—一、项目简介和开发环境准备

    目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ...

  4. SpringSecurity权限管理系统实战—二、日志、接口文档等实现

    系列目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战 ...

  5. SpringSecurity权限管理系统实战—七、处理一些问题

    目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ...

  6. SpringSecurity权限管理系统实战—八、AOP 记录用户、异常日志

    目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ...

  7. SpringSecurity权限管理系统实战—九、数据权限的配置

    目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ...

  8. SpringSecurity权限管理系统实战—三、主要页面及接口实现

    系列目录 前言 后端五分钟,前端半小时.. 每次写js都头疼. 自己写前端是不可能的,这辈子不可能自己写前端的,只能找找别人的模板才能维持的了生存这样子.github,gitee上的模板又多,帮助文档 ...

  9. [权限管理系统篇] (五)-Spring security(授权过程分析)

    欢迎关注公众号[Ccww笔记],原创技术文章第一时间推出 前言 权限管理系统的组件分析以及认证过程的往期文章: Spring security (一)架构框架-Component.Service.Fi ...

随机推荐

  1. Flutter学习笔记(41)--自定义Dialog实现版本更新弹窗

    如需转载,请注明出处:Flutter学习笔记(41)--自定义Dialog实现版本更新弹窗 功能点: 1.更新弹窗UI 2.强更与非强更且别控制 3.屏蔽物理返回键(因为强更的时候点击返回键,弹窗会消 ...

  2. springboot(九)文件上传

    在企业级项目开发过程中,上传文件是最常用到的功能.SpringBoot集成了SpringMVC,当然上传文件的方式跟SpringMVC没有什么出入.下面我们来创建一个SpringBoot项目完成单个. ...

  3. 3.pandas的简单查询

    知道了基本的pandas的数据结构,就可以进行查询相应的数据了 DataFrame可以看成是一个个的Series组成的一个二维结构,既然如此,就会有从DataFrame里查询Series的方法 从Da ...

  4. 2018年5月15日的sqlite安装和数据库记录

    sqlite数据库安装在d:\sqlite_files运行sqlite3查看数据表,命令,.tables 数据库文件 d:\sqlite_files\device.db create table de ...

  5. Visio的快速使用和功能理念

    以前我对visio这种Microsoft办公套件并不感冒,觉得完全没必要用Visio作图,最多用一下Process on,其他基本用Xmind解决问题了.但是最近几次工作需要接触之后发现,Visio还 ...

  6. HTTP Keep-Alive的作用

    作用: Keep-Alive:使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接.Web服务器,基本上都支持HTTP Keep-Alive ...

  7. 一切皆组件的Flutter,安能辨我是雄雌

    从一开始接触Flutter,相信读者都会铭记一句话,那就是--一切皆组件.今天我们就来体会一下这句话的神奇魔力,我们先从实际的产品需求说起. 我们先来看一个简化的运行图: 我们要实现如上图所示的日期选 ...

  8. Java数据结构和算法(2)之稀疏数组

    1.定义 稀疏数组可以看做是普通二位数组的压缩,但是这里说的普通数组是值无效数据量远大于有效数据量的数组,关于稀疏数组的运用有五子棋盘,地图等.. *当一个数组中大部分元素为0,或者为同一个值的数组时 ...

  9. three.js 郭先生制作太阳系

    今天郭先生收到评论,想要之前制作太阳系的案例,因为找不到了,于是在vue版本又制作一版太阳系,在线案例请点击博客原文(加载时间比较长,请稍等一下).话不多说先看效果图. 图片有点多,先放三张,相比于上 ...

  10. Spring学习之AOP的实现方式

    Spring学习之AOP的三种实现方式 一.介绍AOP 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能 ...