本文是接着上篇博客写的:Spring boot 入门(三):SpringBoot 集成结合 AdminLTE(Freemarker),利用 generate 自动生成代码,利用 DataTable 和 PageHelper 进行分页显示。按照前面的博客,已经可以搭建一个简单的 Spring Boot 系统,本篇博客继续对此系统进行改造,主要集成了 Shiro 权限认证框架,关于 Shiro 部分,在本人之前的博客(认证与Shiro安全框架)有介绍到,这里就不做累赘的介绍。

此系列的博客为实践部分,以代码和搭建系统的过程为主,如遇到专业名词,自行查找其含义。

1.Shiro 配置类

系统搭建到目前为止,主要用到了3个配置类,均与 Shiro 有关,后期随着项目的扩大,配置文件也会随之增多。

  • FreeMarkerConfig:主要针对 FreeMarker 页面显示的配置,关于 Shiro 部分,为 Shiro 标签设置了共享变量,如果不设置此变量,FreeMarker 页面将不能识别 Shiro 的标签,其主要代码如下:
 configuration.setSharedVariable("shiro", new ShiroTags());
  • MShiroFilterFactoryBean:设置了过滤器,当然也可以在 Config 文件里面配置过滤器,其缺点是:在每次请求里面都做了 session 的读取和更新访问时间等操作,这样在集群部署 session 共享的情况下,数量级的加大了处理量负载。本项目后期将用到分布式,因此这里就直接将过滤器与 Config 配置文件分离,提高效率。
 private final class MSpringShiroFilter extends AbstractShiroFilter {
protected MSpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
super();
if (webSecurityManager == null) {
throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
}
setSecurityManager(webSecurityManager);
if (resolver != null) {
setFilterChainResolver(resolver);
}
} @Override
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain chain) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String str = request.getRequestURI().toLowerCase();
boolean flag = true;
int idx = 0;
if ((idx = str.indexOf(".")) > 0) {
str = str.substring(idx);
if (ignoreExt.contains(str.toLowerCase()))
flag = false;
}
if (flag) {
super.doFilterInternal(servletRequest, servletResponse, chain);
} else {
chain.doFilter(servletRequest, servletResponse);
}
} }
  • ShiroConfiguration:通用配置文件,此配置文件为 Shiro 的基础通用配置文件,只要是集成 Shiro,必有此文件,主要配置 Shiro 的登录认证相关的信息,其代码如下:
 /**
* 设置shiro的缓存,缓存参数均配置在xml文件中
* @return
*/
@Bean
public EhCacheManager getEhCacheManager() {
EhCacheManager em = new EhCacheManager();
em.setCacheManagerConfigFile("classpath:ehcache/ehcache-shiro.xml");
return em;
}
/**
* 凭证匹配器
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
* 所以我们需要修改下doGetAuthenticationInfo中的代码;
* )
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(1);//散列的次数,比如散列两次,相当于 md5(md5(""));
return hashedCredentialsMatcher;
}
/**
*
* 主文件
*/
@Bean(name = "myShiroRealm")
public UserRealm myShiroRealm(EhCacheManager cacheManager) {
UserRealm realm = new UserRealm();
realm.setCacheManager(cacheManager);
realm.setCredentialsMatcher(hashedCredentialsMatcher());
return realm;
}
//会话ID生成器
@Bean(name = "sessionIdGenerator")
public JavaUuidSessionIdGenerator javaUuidSessionIdGenerator(){
JavaUuidSessionIdGenerator javaUuidSessionIdGenerator = new JavaUuidSessionIdGenerator();
return javaUuidSessionIdGenerator;
}
@Bean(name = "sessionIdCookie")
public SimpleCookie getSessionIdCookie(){
SimpleCookie sessionIdCookie = new SimpleCookie("sid");
sessionIdCookie.setHttpOnly(true);
sessionIdCookie.setMaxAge(-1);
return sessionIdCookie; }
/*<!-- 会话DAO -->*/
@Bean(name = "sessionDAO")
public EnterpriseCacheSessionDAO enterpriseCacheSessionDAO(){
EnterpriseCacheSessionDAO sessionDao = new EnterpriseCacheSessionDAO();
sessionDao.setSessionIdGenerator(javaUuidSessionIdGenerator());
sessionDao.setActiveSessionsCacheName("shiro-activeSessionCache");
return sessionDao;
}
@Bean(name = "sessionValidationScheduler")
public ExecutorServiceSessionValidationScheduler getExecutorServiceSessionValidationScheduler() {
ExecutorServiceSessionValidationScheduler scheduler = new ExecutorServiceSessionValidationScheduler();
scheduler.setInterval(1800000);
return scheduler;
}
@Bean(name = "sessionManager")
public DefaultWebSessionManager sessionManager(EnterpriseCacheSessionDAO sessionDAO){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setGlobalSessionTimeout(1800000);
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setSessionValidationScheduler(getExecutorServiceSessionValidationScheduler());
sessionManager.setSessionDAO(sessionDAO);
sessionManager.setSessionIdCookieEnabled(true);
sessionManager.setSessionIdCookie(getSessionIdCookie());
return sessionManager;
}
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
daap.setProxyTargetClass(true);
return daap;
}
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(UserRealm myShiroRealm, DefaultWebSessionManager sessionManager) {
DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
dwsm.setRealm(myShiroRealm);
// <!-- 用户授权/认证信息Cache, 采用EhCache 缓存 -->
dwsm.setCacheManager(getEhCacheManager());
dwsm.setSessionManager(sessionManager);
return dwsm;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(securityManager);
return aasa;
}
/**
* ShiroFilter<br/>
* 注意这里参数中的 StudentService 和 IScoreDao 只是一个例子,因为我们在这里可以用这样的方式获取到相关访问数据库的对象,
* 然后读取数据库相关配置,配置到 shiroFilterFactoryBean 的访问规则中。实际项目中,请使用自己的Service来处理业务逻辑。
*
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new MShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的连接
shiroFilterFactoryBean.setSuccessUrl("/certification");
//shiroFilterFactoryBean.setSuccessUrl("/index");
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
loadShiroFilterChain(shiroFilterFactoryBean);
return shiroFilterFactoryBean;
}
/**
* 加载shiroFilter权限控制规则(从数据库读取然后配置)
*
*/
private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean){
/////////////////////// 下面这些规则配置最好配置到配置文件中 ///////////////////////
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter
filterChainDefinitionMap.put("/login", "authc");
filterChainDefinitionMap.put("/logout", "logout");
// anon:它对应的过滤器里面是空的,什么都没做
logger.info("##################从数据库读取权限规则,加载到shiroFilter中##################");
// filterChainDefinitionMap.put("/user/edit/**", "authc,perms[user:edit]");// 这里为了测试,固定写死的值,也可以从数据库或其他配置中读取
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
}

2.登录认证与权限管理

主要重写了 Realm域,完成权限认证和权限管理:

     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//如果没有做权限验证,此处只需要return null即可
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
String userName = (String) principals.getPrimaryPrincipal();
Result<TUser> list = userService.getUserByUsername(userName);
if(list.isStatus()) {
//获取该用户所属的角色
Result<List<TRole>> resultRole = roleService.getRoleByUserId(list.getResultData().getUserId());
if(resultRole.isStatus()) {
HashSet<String> role = new HashSet<String>();
for(TRole tRole : resultRole.getResultData()) {
role.add(tRole.getRoleId()+"");
}
//获取该角色拥有的权限
Result<List<TPermission>> resultPermission = permissionService.getPermissionsByRoleId(role);
if(resultPermission.isStatus()) {
HashSet<String> permissions = new HashSet<String>();
for(TPermission tPermission : resultPermission.getResultData()) {
permissions.add(tPermission.getPermissionsValue());
}
System.out.println("权限:"+permissions);
authorizationInfo.setStringPermissions(permissions);
}
}
}
//return null;
return authorizationInfo;
} @Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//认证登录
String username = (String) authenticationToken.getPrincipal();
//String password = new String((char[]) authenticationToken.getCredentials());
Result<TUser> result = userService.getUserByUsername(username);
if (result.isStatus()) {
TUser user = result.getResultData();
return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName());
}
//return new SimpleAuthenticationInfo(user., "123456", getName());
return null;
}
}

2.1.登录认证

首先创建一个前端登录界面,做一个简单的登录 Form 表单

点击登录即想后台发送一个请求,必须是Post请求,否则Shiro不能识别,认证部分主要在 Ream 中完成,新建一个类,继承 AuthorizingRealm ,然后在重写 doGetAuthenticationInfo 方法:

 

只需要通过界面上的用户名查找到数据库存储的相关信息即可,具体的认证是 Shiro 内部自己完成的,我们只需要传入数据库中存储的用户名和密码个认证函数即可(new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName())),我们可以自己重新定义密码比较器,密码比较器的写法较多,在认证与Shiro安全框架中,直接将密码比较器写入到Ream中,耦合度太高,本项目通过配置的方式重写密码比较器,具体代码请参考参考ShiroConfiguration配置类:

在具体的 Login 方法中,写入一些登录失败的异常即可,主要用户将此失败结果存入 Session,并显示在页面上:

     @RequestMapping(value = "/login", method = RequestMethod.POST)
public String postLogin(RedirectAttributes redirectAttributes, HttpServletRequest request, HttpSession session) {
// 登录失败从request中获取shiro处理的异常信息。
// shiroLoginFailure:就是shiro异常类的全类名.
String exception = (String) request.getAttribute("shiroLoginFailure"); System.out.println("exception=" + exception);
String msg = "";
if (exception != null) {
if (UnknownAccountException.class.getName().equals(exception)) {
System.out.println("UnknownAccountException -- > 账号不存在:");
msg = "用户不存在!";
} else if (IncorrectCredentialsException.class.getName().equals(exception)) {
System.out.println("IncorrectCredentialsException -- > 密码不正确:");
msg = "密码不正确!";
} else if ("kaptchaValidateFailed".equals(exception)) {
System.out.println("kaptchaValidateFailed -- > 验证码错误");
msg = "验证码错误!";
} else {
//msg = "else >> "+exception;
msg = "密码不正确!";
System.out.println("else -- >" + exception);
}
}
redirectAttributes.addFlashAttribute("msg", msg);
session.setAttribute("msg", msg);
//return redirect("/login");
return "redirect:login";
//return msg;
}

此时登录认证部门已经完成:一个页面+后台2个函数(1个认证函数+1个Login函数)

2.2.权限管理

总体来说,权限管理只需要在界面增加 Shiro 的权限标签即可,可以使用角色的标签,也可以使用权限的标签,一般情况下2种标签配合使用,效果最好 <@shiro.hasPermission name="xtgl-yhgl:read"> <@shiro.hasRolen name="xtgl-yhgl:read">

此外,在 Realm 中,需要重写权限认证的业务逻辑,通常情况下通过用户 ID 找到该用户所属的角色,然后通过角色 ID 找到该角色拥有的权限,并将角色或者权限写入的 Shiro 中即可: authorizationInfo.setStringPermissions(permissions); authorizationInfo.setRoles(role);

本项目也是通过此逻辑完成权限管理的

上面2张截图表示的是一个函数。

到此,Spring Boot集成Shiro框架的权限认证已经搭建完毕,可以实现简单的权限管理。

3.新增文件

较上一篇博客,Shiro 部分新增加的文件

Spring boot 入门(四):集成 Shiro 实现登陆认证和权限管理的更多相关文章

  1. Spring Boot入门(四):开发Web Api接口常用注解总结

    本系列博客记录自己学习Spring Boot的历程,如帮助到你,不胜荣幸,如有错误,欢迎指正! 在程序员的日常工作中,Web开发应该是占比很重的一部分,至少我工作以来,开发的系统基本都是Web端访问的 ...

  2. Spring集成shiro做登陆认证

    一.背景 其实很早的时候,就在项目中有使用到shiro做登陆认证,直到今天才又想起来这茬,自己抽空搭了一个spring+springmvc+mybatis和shiro进行集成的种子项目,当然里面还有很 ...

  3. Spring Cloud之路:(七)SpringBoot+Shiro实现登录认证和权限管理

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/sage_wang/article/details/79592269一.Shiro介绍1.Shiro是 ...

  4. spring boot 2.0 集成 shiro 和 pac4j cas单点登录

    新开的项目,果断使用  spring boot  最新版本  2.0.3 ,免得后期升级坑太多,前期把雷先排了. 由于对 shiro 比较熟,故使用 shiro 来做权限控制.同时已经存在了 cas  ...

  5. Spring boot 入门四:spring boot 整合mybatis 实现CRUD操作

    开发环境延续上一节的开发环境这里不再做介绍 添加mybatis依赖 <dependency> <groupId>org.mybatis.spring.boot</grou ...

  6. Spring Boot确保Web应用安全(登陆认证)

    Spring Boot官方提供了一个登陆认证的清晰易懂的 例子 , 我们在次以此例展开演示Spring Boot是如何实现登陆认证的. 首先我们去 https://start.spring.io/ 下 ...

  7. Apache Shiro(五)-登录认证和权限管理ssm

    创建一个web动态项目 jar包 web.xml web.xml做了如下几件事情1. 指定spring的配置文件有两个 applicationContext.xml: 用于链接数据库的 applica ...

  8. Apache Shiro(二)-登录认证和权限管理数据库操作

    数据库支持 在上一篇中使用ini 配置文件进行了相关权限数据的配置. 但是实际工作中,我们都会把权限相关的内容放在数据库里. 所以本知识点讲解如何放在数据库里来撸. RBAC 概念 RBAC 是当下权 ...

  9. Apache Shiro(一)-登录认证和权限管理初识

    What is Apache Shiro? Apache Shiro是一个功能强大.灵活的,开源的安全框架.它可以干净利落地处理身份验证.授权.企业会话管理和加密. Apache Shiro的首要目标 ...

随机推荐

  1. Python与家国天下

    导读:Python猫是一只喵星来客,它爱地球的一切,特别爱优雅而无所不能的 Python.我是它的人类朋友豌豆花下猫,被授权润色与发表它的文章.如果你是第一次看到这个系列文章,那我强烈建议,请先看看它 ...

  2. SpringCloud学习系列之五-----配置中心(Config)和消息总线(Bus)完美使用版

    前言 在上篇中介绍了SpringCloud Config的使用,本篇则介绍基于SpringCloud(基于SpringBoot2.x,.SpringCloud Finchley版)中的分布式配置中心( ...

  3. vue中路由按需加载的几种方式

    使用vue-cli构建项目后,我们会在Router文件夹下面的index.js里面引入相关的路由组件,如: import Hello from '@/components/Hello' import ...

  4. MES是什么,MOM是什么?

    MES简介 MES(Manufacturing Execution System)制造执行系统,用于制造业车间级的信息化管理系统.单说MES系统这个概念比较广泛,大致分为以下几个方面:计划调度.生产. ...

  5. 一起学Android之Dialog

    概述 对话框(Dialog)是一个小窗口,在Android系统开发中经常会用到,它提示用户做决定或者输入一些东西,对话框并不填充屏幕,是一个模态(Modal)窗口.Dialog类是所有对话框的基类,应 ...

  6. linux用curl发送post请求

    1.curl -X POST “http://XXXXXXX”这种请求方式参数直接写在URL里面的,而不是body

  7. Linux记录~持续更新~

    ls -ildha /etc -i 显示对应id号 唯一标识 -l 显示详情 -d 显示当前文件夹 不包括子目录 -h 单位为KB 而不是B -a 显示所有 包括隐藏文件 mkdir mkdir -p ...

  8. Git报错 bad numeric config value '100000' for 'pack.windowmemory': out of range

    Git报错 bad numeric config value '10240M' for 'pack.windowmemory': out of range $ git config --edit -- ...

  9. 理解Linux文档的默认安全机制、隐藏属性、特殊权限,妈妈在也不用担心你从删库到跑路!!!

    写在前面 前面的章节 详解Linux文档属性.拥有者.群组.权限.差异,介绍了文档的基本权限,包括读写执行(r,w,x),还有文档若干的属性,包括是否为目录(d).文件(-).链接文件(l).拥有者. ...

  10. 从0到1打造自己的VOIP网络电话系统(基于FreePBX)

    从0到1打造自己的网络电话系统 最近流量卡越来越便宜了,看看自己手里的"坑不死老用户"的联通卡,顿时感觉到深深的恶意,但是iPhone没有双卡功能,所以只好自己动手打造一个网络电话 ...