spring boot 集成 shiro 自定义密码验证 自定义freemarker标签根据权限渲染不同页面(推荐

项目里一直用的是 spring-security ,不得不说,spring-security 真是东西太多了,学习难度太大(可能我比较菜),这篇博客来总结一下折腾shiro的成果,分享给大家,强烈推荐shiro,真心简单 : )

引入依赖

<dependency>
 <groupId>org.apache.shiro</groupId>
 <artifactId>shiro-spring</artifactId>
 <version>1.4.0</version>
</dependency>

用户,角色,权限

就是经典的RBAC权限系统,下面简单给一下实体类字段

AdminUser.java

public class AdminUser implements Serializable {

 private static final long serialVersionUID = 8264158018518861440L;
 private Integer id;
 private String username;
 private String password;
 private Integer roleId;
 // getter setter...
}

Role.java

public class Role implements Serializable {
 private static final long serialVersionUID = 7824693669858106664L;
 private Integer id;
 private String name;
 // getter setter...
}

Permission.java

public class Permission implements Serializable {
 private static final long serialVersionUID = -2694960432845360318L;
 private Integer id;
 private String name;
 private String value;
 // 权限的父节点的id
 private Integer pid;
 // getter setter...
}

自定义Realm

这货就是查询用户的信息然后放在shiro的个人用户对象的缓存里,shiro自己有一个session的对象(不是servlet里的session)作用就是后面用户发起请求的时候拿来判断有没有权限

另一个作用是查询一下用户的信息,将用户名,密码组装成一个 AuthenticationInfo 用于后面密码校验的

具体代码如下

MyShiroRealm.java

@Component
public class MyShiroRealm extends AuthorizingRealm {
 private Logger log = LoggerFactory.getLogger(MyShiroRealm.class);
 @Autowired
 private AdminUserService adminUserService;
 @Autowired
 private RoleService roleService;
 @Autowired
 private PermissionService permissionService;
 // 用户权限配置
 @Override
 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
 //访问@RequirePermission注解的url时触发
 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
 AdminUser adminUser = adminUserService.selectByUsername(principals.toString());
 //获得用户的角色,及权限进行绑定
 Role role = roleService.selectById(adminUser.getRoleId());
 // 其实这里也可以不要权限那个类了,直接用角色这个类来做鉴权,
 // 不过角色包含很多的权限,已经算是大家约定的了,所以下面还是查询权限然后放在AuthorizationInfo里
 simpleAuthorizationInfo.addRole(role.getName());
 // 查询权限
 List<Permission> permissions = permissionService.selectByRoleId(adminUser.getRoleId());
 // 将权限具体值取出来组装成一个权限String的集合
 List<String> permissionValues = permissions.stream().map(Permission::getValue).collect(Collectors.toList());
 // 将权限的String集合添加进AuthorizationInfo里,后面请求鉴权有用
 simpleAuthorizationInfo.addStringPermissions(permissionValues);
 return simpleAuthorizationInfo;
 }
 // 组装用户信息
 @Override
 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
 String username = (String) token.getPrincipal();
 log.info("用户:{} 正在登录...", username);
 AdminUser adminUser = adminUserService.selectByUsername(username);
 // 如果用户不存在,则抛出未知用户的异常
 if (adminUser == null) throw new UnknownAccountException();
 return new SimpleAuthenticationInfo(username, adminUser.getPassword(), getName());
 }
}

实现密码校验

shiro内置了几个密码校验的类,有 Md5CredentialsMatcher Sha1CredentialsMatcher , 不过从1.1版本开始,都开始使用 HashedCredentialsMatcher 这个类了,通过配置加密规则来校验

它们都实现了一个接口 CredentialsMatcher 我这里也实现这个接口,实现一个自己的密码校验

说明一下,我这里用的加密方式是Spring-Security里的 BCryptPasswordEncoder 作的加密,之所以用它,是因为同一个密码被这货加密后,密文都不一样,下面是具体代码

public class MyCredentialsMatcher implements CredentialsMatcher {

 @Override
 public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
 // 大坑!!!!!!!!!!!!!!!!!!!
 // 明明token跟info两个对象的里的Credentials类型都是Object,断点看到的类型都是 char[]
 // 但是!!!!! token里转成String要先强转成 char[]
 // 而info里取Credentials就可以直接使用 String.valueOf() 转成String
 // 醉了。。
 String rawPassword = String.valueOf((char[]) token.getCredentials());
 String encodedPassword = String.valueOf(info.getCredentials());
 return new BCryptPasswordEncoder().matches(rawPassword, encodedPassword);
 }
}

配置shiro

因为项目是spring-boot开发的,shiro就用java代码配置,不用xml配置, 具体配置如下

@Configuration
public class ShiroConfig {
 private Logger log = LoggerFactory.getLogger(ShiroConfig.class);
 @Autowired
 private MyShiroRealm myShiroRealm;
 @Bean
 public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
 log.info("开始配置shiroFilter...");
 ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
 shiroFilterFactoryBean.setSecurityManager(securityManager);
 //拦截器.
 Map<String,String> map = new HashMap<>();
 // 配置不会被拦截的链接 顺序判断 相关静态资源
 map.put("/static/**", "anon");
 //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
 map.put("/admin/logout", "logout");
 //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
 //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
 map.put("/admin/**", "authc");
 // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
 shiroFilterFactoryBean.setLoginUrl("/adminlogin");
 // 登录成功后要跳转的链接
 shiroFilterFactoryBean.setSuccessUrl("/admin/index");
 //未授权界面;
 shiroFilterFactoryBean.setUnauthorizedUrl("/error");
 shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
 return shiroFilterFactoryBean;
 }
 // 配置加密方式
 // 配置了一下,这货就是验证不过,,改成手动验证算了,以后换加密方式也方便
 @Bean
 public MyCredentialsMatcher myCredentialsMatcher() {
 return new MyCredentialsMatcher();
 }
 // 安全管理器配置
 @Bean
 public SecurityManager securityManager() {
 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
 myShiroRealm.setCredentialsMatcher(myCredentialsMatcher());
 securityManager.setRealm(myShiroRealm);
 return securityManager;
 }
}

登录

都配置好了,就可以发起登录请求做测试了,一个简单的表单即可,写在Controller里就行

@PostMapping("/adminlogin")
public String adminLogin(String username, String password,
       @RequestParam(defaultValue = "0") Boolean rememberMe,
       RedirectAttributes redirectAttributes) {
 try {
 // 添加用户认证信息
 Subject subject = SecurityUtils.getSubject();
 if (!subject.isAuthenticated()) {
  UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);
  //进行验证,这里可以捕获异常,然后返回对应信息
  subject.login(token);
 }
 } catch (AuthenticationException e) {
 // e.printStackTrace();
 log.error(e.getMessage());
 redirectAttributes.addFlashAttribute("error", "用户名或密码错误");
 redirectAttributes.addFlashAttribute("username", username);
 return redirect("/adminlogin");
 }
 return redirect("/admin/index");
}

从上面代码可以看出,记住我功能也直接都实现好了,只需要在组装 UsernamePasswordToken 的时候,将记住我字段传进去就可以了,值是 true, false, 如果是true,登录成功后,shiro会在本地写一个cookie

调用 subject.login(token); 方法后,它会去鉴权,期间会产生各种各样的异常,有以下几种,可以通过捕捉不同的异常然后提示页面不同的错误信息,相当的方便呀,有木有

  • AccountException 帐户异常
  • ConcurrentAccessException 这个好像是并发异常
  • CredentialsException 密码校验异常
  • DisabledAccountException 帐户被禁异常
  • ExcessiveAttemptsException 尝试登录次数过多异常
  • ExpiredCredentialsException 认证信息过期异常
  • IncorrectCredentialsException 密码不正确异常
  • LockedAccountException 帐户被锁定异常
  • UnknownAccountException 未知帐户异常
  • UnsupportedTokenException login(AuthenticationToken) 这个方法只能接收 AuthenticationToken 类的对象,如果传的是其它的类,就抛这个异常

上面这么多异常,shiro在处理登录的逻辑时,会自动的发出一些异常,当然你也可以手动去处理登录流程,然后根据不同的问题抛出不同的异常,手动处理的地方就在自己写的 MyShiroRealm 里的 doGetAuthenticationInfo() 方法里,我在上面代码里只处理了一个帐户不存在时抛出了一个 UnknownAccountException 的异常,其实还可以加更多其它的异常,这个要看个人系统的需求来定了

到这里已经可以正常的实现登录了,下面来说一些其它相关的功能的实现

自定freemarker标签

开发项目肯定要用到页面模板,我这里用的是 freemarker ,一个用户登录后,页面可能要根据用户的不同权限渲染不同的菜单,github上有个开源的库,也是可以用的,不过我觉得那个太麻烦了,就自己实现了一个,几行代码就能搞定

ShiroTag.java

@Component
public class ShiroTag {
 // 判断当前用户是否已经登录认证过
 public boolean isAuthenticated(){
 return SecurityUtils.getSubject().isAuthenticated();
 }
 // 获取当前用户的用户名
 public String getPrincipal() {
 return (String) SecurityUtils.getSubject().getPrincipal();
 }
 // 判断用户是否有 xx 角色
 public boolean hasRole(String name) {
 return SecurityUtils.getSubject().hasRole(name);
 }
 // 判断用户是否有 xx 权限
 public boolean hasPermission(String name) {
 return !StringUtils.isEmpty(name) && SecurityUtils.getSubject().isPermitted(name);
 }
}

将这个类注册到freemarker的全局变量里

FreemarkerConfig.java

@Configuration
public class FreemarkerConfig {
 private Logger log = LoggerFactory.getLogger(FreeMarkerConfig.class);
 @Autowired
 private ShiroTag shiroTag;
 @PostConstruct
 public void setSharedVariable() throws TemplateModelException {
 //注入全局配置到freemarker
 log.info("开始配置freemarker全局变量...");
 // shiro鉴权
 configuration.setSharedVariable("sec", shiroTag);
 log.info("freemarker自定义标签配置完成!");
 }
}

有了这些配置后,就可以在页面里使用了,具体用法如下

<#if sec.hasPermission("topic:list")>
 <li <#if page_tab=='topic'>class="active"</#if>>
 <a href="/admin/topic/list" rel="external nofollow" >
  <i class="fa fa-list"></i>
  <span>话题列表</span>
 </a>
 </li>
</#if>

加上这个后,在渲染页面的时候,就会根据当前用户是否有查看话题列表的权限,然后来渲染这个菜单

注解权限

有了上面freemarker标签判断是否有权限来渲染页面,这样做只能防君子,不能防小人,如果一个人知道后台的某个访问链接,但这个链接它是没有权限访问的,那他只要手动输入这个链接就还是可以访问的,所以这里还要在Controller层加一套防御,具体配置如下

在ShiroConfig里加上两个Bean

//加入注解的使用,不加入这个注解不生效
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
 AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
 authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
 return authorizationAttributeSourceAdvisor;
}
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
 DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
 defaultAAP.setProxyTargetClass(true);
 return defaultAAP;
}

有了这两个Bean就可以用shiro的注解鉴权了,用法如下 @RequiresPermissions("topic:list")

@Controller
@RequestMapping("/admin/topic")
public class TopicAdminController extends BaseAdminController {

 @RequiresPermissions("topic:list")
 @GetMapping("/list")
 public String list() {
 // TODO
 return "admin/topic/list";
 }
}

shiro除了 @RequiresPermissions 注解外,还有其它几个鉴权的注解

  • @RequiresPermissions
  • @RequiresRoles
  • @RequiresUser
  • @RequiresGuest
  • @RequiresAuthentication

一般 @RequiresPermissions 就够用了

总结

spring-boot 集成 shiro 到这就结束了,是不是网上能找到的教程里最全的!相比 spring-security 要简单太多了,强烈推荐

(0)

相关推荐

  • springboot整合freemarker详解

    前提: 开发工具:idea 框架:spring boot.maven 1.pom文件添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> <version>1.4.1.RELEASE</version> </dependency>

  • Spring Boot使用模板freemarker的示例代码

    最近有好久没有更新博客了,感谢小伙伴的默默支持,不知道是谁又打赏了我一个小红包,谢谢. 今天我们讲讲怎么在Spring Boot中使用模板引擎freemarker,先看看今天的大纲: (1) freemarker介绍: (2) 新建spring-boot-freemarker工程: (3) 在pom.xml引入相关依赖: (4) 编写启动类: (5) 编写模板文件hello.ftl; (6) 编写访问类HelloController; (7) 测试: (8) freemarker配置: (9)

  • java、freemarker保留两位小数

     一.Java保留2位小数 double acc = 22.4322; String accX = String.format("%.2f", acc); 二.freemarker保留两位小数 <#if centerFreeSize??> ${centerFreeSize?string("#.##")} <#else> 0.00 </#if> 补充:freemarker保留小数 freemarker保留两位小数 方法一 #{num

  • Java实现用Freemarker完美导出word文档(带图片)

    前言 最近在项目中,因客户要求,将页面内容(如合同协议)导出成word,在网上翻了好多,感觉太乱了,不过最后还是较好解决了这个问题. 准备材料 1.word原件 2.编辑器(推荐Firstobject free XML editor) 实现步骤 1.用Microsoft Office Word打开word原件: 2.把需要动态修改的内容替换成***,如果有图片,尽量选择较小的图片几十K左右,并调整好位置: 3.另存为,选择保存类型Word 2003 XML 文档(*.xml)[这里说一下为什么用

  • SpringBoot整合freemarker的讲解

    freemarker和thymeleaf是模板引擎.在早前我们使用Struts或者SpringMVC等框架的时候,使用的都是jsp,jsp的本质其实就是一个Servlet,其中的数据需要在后端进行渲染,然后再在客户端显示,效率比较低下.而模板引擎恰恰相反,其中的数据渲染是在客户端,效率方面比较理想一点.前后端不分离的话用模板引擎比较好,前后端分离的话其实用处并不大很大.Spring官方比较推荐的是thymeleaf,其文件后缀是html.本篇文章我们主要来看看SpringBoot整合freema

  • spring boot里增加表单验证hibernate-validator并在freemarker模板里显示错误信息(推荐)

    创建项目 使用IDEA创建一个spring-boot项目,依赖选上 web, validation, freemarker 即可 先看看效果 创建实体类 创建并加上注解,代码如下 public class Person implements Serializable { @NotNull @Length(min = 3, max = 10) // username长度在3-10之间 private String username; @NotNull @Min(18) // 年龄最小要18岁 pr

  • Java用freemarker导出word实用示例

    最近一个项目要导出word文档,折腾老半天,发现还是用freemarker的模板来搞比较方便省事,现总结一下关键步骤,供大家参考,这里是一个简单的试卷生成例子. 一.模板的制作 先用Word做一个模板,如下图: (注意,上面是有表格的,我设置了边框不可见)然后另存为XML文件,之后用工具打开这个xml文件,有人用firstobject XML Editor感觉还不如notepad++,我这里用notepad++,主要是有高亮显示,和元素自动配对,效果如下: 上面黑色的地方基本是我们之后要替换的地

  • springmvc整合freemarker配置的详细步骤

    一.对应的导包(有些包是不必须的) <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/maven-v4_0_0.xsd">

  • FreeMarker配置(Configuration)

    p> 基础 Configuration 是一个存放应用级别(application level)公共配置信息,以及模版(Template)可使用的全局共享变量的一个对象.同时它还负责模版(Template)实例的创建以及缓存.Configuration 实际上是freemarker.template.Configuration 对象的实例,使用其构造函数创建.通常应用使用一个共享的单实例Configuration 对象. Configuration 对象可被Template 对象的方法使用,每一

  • 详解MyEclipse中搭建spring-boot+mybatis+freemarker框架

    1.在MyEclipse里创建一个maven项目.File>New>Maven Project: 勾选图中红色部分,然后点击Next. 2.填写下图中红色部分然后点击Finish. 3.此时一个maven项目已经生成,目录结构如下: 4.打开pom.xml在里面编辑如下内容: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSche

随机推荐