springboot不像springmvc,它没有xml配置文件,那该如何配置shiro呢,其实也不难,用java代码+注解来解决这个问题。仅以此篇记录我对shiro的学习,如有对过客造成不便,实在抱歉!

  一、加入jar包

  既然要用到shiro,当然要加入它的jar包咯,在pom.xml中jar包依赖中加入:

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

  二、写实体类

  这需要三个实体类,hibernate自动生成5个表

  User实体(用户):

package com.cy.coo.bean;

import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany; import com.fasterxml.jackson.annotation.JsonBackReference; @Entity
public class User { @Id
@GeneratedValue
private Integer user_id;//用户序号 @Column(unique=true)
private String name;//账户 private String password;//密码 private String salt;//盐 private Integer state;//用户状态 private String createtime;//创建时间 @ManyToMany(fetch=FetchType.EAGER)//立即从数据库中进行加载数据;
@JoinTable(name="User_Role",joinColumns={@JoinColumn(name="user_id")},
inverseJoinColumns={@JoinColumn(name="role_id")})
private List<Role> roleList; @JsonBackReference
public List<Role> getRoleList(){
return roleList;
} public void setRoleList(List<Role> roleList){
this.roleList=roleList;
} 注:其它getter和setter省略 }

  关于为什么要在getRolelist这个方法上加上@JsonBackReference注解,可以查看这篇文章http://blog.csdn.net/maxu12345/article/details/45538157

  Role实体(角色):

package com.cy.coo.bean;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List; import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany; import com.fasterxml.jackson.annotation.JsonBackReference; @Entity
public class Role implements Serializable {
private static final long serialVersionUID = 1L; @Id
@GeneratedValue
private Integer role_id;//角色序号 private String role_name;//角色名称 private String role_description;//角色描述 @ManyToMany
@JoinTable(name = "User_Role", joinColumns = { @JoinColumn(name = "role_id") }, inverseJoinColumns = {
@JoinColumn(name = "user_id") })
private List<User> userList=new ArrayList<>(); @ManyToMany(fetch=FetchType.EAGER)
@JoinTable(name="Role_Function",joinColumns={@JoinColumn(name="role_id")},inverseJoinColumns={
@JoinColumn(name="function_id")})
private List<Function> functionList=new ArrayList<>(); @JsonBackReference
public List<Function> getFunctionList(){
return functionList;
} public void setFunctionList(List<Function> functionList){
this.functionList=functionList;
} @JsonBackReference
public List<User> getUserList() {
return userList;
} public void setUserList(List<User> userList) {
this.userList = userList;
} public Integer getRole_id() {
return role_id;
} public void setRole_id(Integer role_id) {
this.role_id = role_id;
} public String getRole_name() {
return role_name;
} public void setRole_name(String role_name) {
this.role_name = role_name;
} public String getRole_description() {
return role_description;
} public void setRole_description(String role_description) {
this.role_description = role_description;
} }

  Function实体(权限):

package com.cy.coo.bean;

import java.util.List;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany; @Entity
public class Function { @Id
@GeneratedValue
private Integer function_id;//功能序号 private String permission;//权限字符串 @ManyToMany
@JoinTable(name = "Role_Function", joinColumns = { @JoinColumn(name = "function_id") }, inverseJoinColumns = {
@JoinColumn(name = "role_id") })
private List<Role> roleList; public List<Role> getRoleList() {
return roleList;
} public void setRoleList(List<Role> roleList) {
this.roleList = roleList;
} public Integer getFunction_id() {
return function_id;
} public void setFunction_id(Integer function_id) {
this.function_id = function_id;
} public String getPermission() {
return permission;
} public void setPermission(String permission) {
this.permission = permission;
} }

  这几个实体类的具体关系如下图,也完美的解释了为什么会生成5张表:

  三、写一个与前端交互的controller方法,service层的具体逻辑的方法

    @PostMapping(value = "/logon")
public Object logon(@RequestBody Login user) { return userService.login(user);
}

  这个方法就是将前端传来的username和password封装到Login类中,Login类也只有这两个属性,然后调用Service层的login方法来处理。下面是service的login方法:

/**
* 用户登录 create by yyc 2017年5月12日下午4:31:26
*/
@Override
public Object login(Login user) {
String username = user.getUsername().trim();
String password = user.getPassword().trim(); // 检查空值
if (!CheckObjectField.CheckField(user)) {
throw new ResultException(CheckObjectField.FieldName + "为空!");
} // 检查用户状态
Integer userState = userRepository.findUserState(username);
if (new Integer("1").equals(userState)) {
throw new ResultException("该用户已锁定");
} // 1、获取Subject实例对象
Subject currentUser = SecurityUtils.getSubject(); // 2、判断当前用户是否登录
if (currentUser.isAuthenticated() == false) {
// 3、将用户名和密码封装到UsernamePasswordToken
UsernamePasswordToken token = new UsernamePasswordToken(username, password); // 4、认证
try {
currentUser.login(token);// 传到MyAuthorizingRealm类中的方法进行认证
Session session = currentUser.getSession();
session.setAttribute("username", username);
} catch (AuthenticationException e) {
throw new ResultException("密码或用户名错误");
}
}
// 根据用户名查询角色信息
List<String> RoleNames = roleService.findRoleName(username); return new LoginReturn(username, RoleNames); }

  service中主要是将用户名和密码封装到shiro的UsernamePasswordToken中,然后将token对象放到SecurityUtils.getSubject()的login方法中,以便shiro认证登录使用。认证失败就会抛出AuthenticationException这个异常,就对异常进行相应的操作,这里的处理是抛出一个自定义异常ResultException。

  四、写我认为的shiro的核心类

package com.cy.coo.shiro;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import com.cy.coo.bean.Function;
import com.cy.coo.bean.Role;
import com.cy.coo.bean.User;
import com.cy.coo.service.UserService; /**
*
*
* @author E-mail:34782655@qq.com
* @version 创建时间:2017年5月8日 上午10:50:50
* 类说明:
* --
*/
public class MyAuthorizingRealm extends AuthorizingRealm { private final static Logger logger=LoggerFactory.getLogger(MyAuthorizingRealm.class); @Autowired
private UserService userService;
//shiro的权限配置方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { logger.info("权限配置-->doGetAuthorizationInfo"); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
logger.info("----------------------------->"+principals.getPrimaryPrincipal());
User user=(User) principals.getPrimaryPrincipal();
for(Role role:user.getRoleList()){
authorizationInfo.addRole(role.getRole_name());
for(Function function:role.getFunctionList()){
authorizationInfo.addStringPermission(function.getPermission());
}
} logger.info("用户"+user.getName()+"具有的角色:"+authorizationInfo.getRoles());
logger.info("用户"+user.getName()+"具有的权限:"+authorizationInfo.getStringPermissions()); return authorizationInfo;
} //shiro的身份验证方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { logger.info("正在验证身份...");
SimpleAuthenticationInfo info=null; //将token转换成UsernamePasswordToken
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
//从转换后的token中获取用户名
String username= upToken.getUsername();
logger.info("----->"+username); //查询数据库,得到用户
User user=userService.findByName(username);
if(user==null){
return null;
} //得到加密密码的盐值
ByteSource salt = ByteSource.Util.bytes(user.getSalt());
// logger.info("加密密码的盐:"+salt);
// //得到盐值加密后的密码:只用于方便数据库测试,后期不会用到。
// Object md = new SimpleHash("MD5",upToken.getPassword(),salt,1024);
// logger.info("盐值加密后的密码:"+md); info = new SimpleAuthenticationInfo(
user, //用户名
user.getPassword(), //密码
salt, //加密的盐值
getName() //realm name
);
return info;
} }

  这个类继承shiro的AuthorizingRealm ,主要有两个方法,一个是权限配置,一个是身份认证,权限配置:当我们要用到权限时shiro会回调doGetAuthorizationInfo这个方法,对当前的用户分配权限,这个方法中的嵌套for循环是怎么回事呢,其实就是将数据库中的对应角色、权限放进shiro中,让他来管理,这需要实体类User中有getRoleList()、getRole_name()和getFunctionList()、getPermission这几个方法,这几个个方法就是设计数据库和实体类时的东西了,关于shiro权限相关的实体类在前面已经给出了。身份认证:在用户登录认证的时候回调,认证失败就抛出AuthenticationException。

  五、shiro配置类

package com.cy.coo.shiro;

import java.util.LinkedHashMap;
import java.util.Map; import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource; @Configuration // 等价于beans
public class ShiroConfig { private static final Logger log = LoggerFactory.getLogger(ShiroFilterFactoryBean.class); @Bean(name = "securityManager")
public SecurityManager securityManager(@Qualifier("authRealm") MyAuthorizingRealm authRealm,
@Qualifier("cookieRememberMeManager") CookieRememberMeManager cookieRememberMeManager) {
log.info("securityManager()");
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(authRealm); // 设置rememberMe管理器
securityManager.setRememberMeManager(cookieRememberMeManager); return securityManager;
} /**
* realm
*
* @return
*/
@Bean(name = "authRealm")
public MyAuthorizingRealm myAuthRealm(
@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher,
@Qualifier("ehCacheManager") EhCacheManager ehCacheManager) {
log.info("myShiroRealm()");
MyAuthorizingRealm myAuthorizingRealm = new MyAuthorizingRealm();
// 设置密码凭证匹配器
myAuthorizingRealm.setCredentialsMatcher(matcher); // myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
// 设置缓存管理器
myAuthorizingRealm.setCacheManager(ehCacheManager); return myAuthorizingRealm;
}     /**
* 缓存管理器
* @return
*/
@Bean(value="ehCacheManager")
public EhCacheManager ehCacheManager(@Qualifier("ehCacheManagerFactoryBean") EhCacheManagerFactoryBean bean) {
log.info("ehCacheManager()");
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
return cacheManager;
} /**
* cookie对象;
*
* @return
*/
@Bean
public SimpleCookie rememberMeCookie() {
log.info("rememberMeCookie()");
// 这个参数是cookie的名称,对应前端的checkbox 的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
// <!-- 记住我cookie生效时间30天(259200) ,单位秒;-->
simpleCookie.setMaxAge(259200);
return simpleCookie;
} /**
* 记住我管理器 cookie管理对象;
*
* @return
*/
@Bean(name = "cookieRememberMeManager")
public CookieRememberMeManager rememberMeManager() {
System.out.println("rememberMeManager()");
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
return cookieRememberMeManager;
} /**
* 密码匹配凭证管理器
*
* @return
*/
@Bean(name = "hashedCredentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
log.info("hashedCredentialsMatcher()");
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("MD5");// 散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(1024);// 散列的次数,比如散列两次,相当于
// md5(md5("")); return hashedCredentialsMatcher;
} /**
* 开启shiro aop注解支持. 使用代理方式;所以需要开启代码支持; Controller才能使用@RequiresPermissions
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
@Qualifier("securityManager") SecurityManager securityManager) {
log.info("authorizationAttributeSourceAdvisor()");
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
} @Bean
public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager) {
log.info("shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager); // 拦截器.
Map<String, String> map = new LinkedHashMap<String, String>(); map.put("/logout", "logout");
map.put("/login", "anon");
map.put("/logon", "anon"); map.put("/**", "authc"); // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized"); shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
}

  这个没什么好说的,最后一个类是shiro的过滤器配置。可以看到我在每个方法上面加了一个@Bean(name="..."),其实这是spring的注解,将这个类放到spring容器中管理,在方法形参中使用@Qualifier(...)来使用它,以致于我们在方法体中调用某个方法时就方面多了。

在这里,关于shiro在springboot中的基础配置就完成了。下面是期间遇到的错误解决方案:

错误一:关于实体类的错误,springboot 中hibernate懒加载  报错....................................No  Session
解决方法:新建类 配置OpenEntityManagerInViewFilter    以及  上面角色类(表)和用户类(表)(Role、User)Role的红色字体也是必须的,及@ManyToMany(fetch=FetchType.EAGER)
由于博主基础的局限还不知道具体的原因是什么,但是解决了就好。
@Configuration
public class HibernateConfig {
@Bean
public FilterRegistrationBean registerOpenEntityManagerInViewFilterBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
OpenEntityManagerInViewFilter filter = new OpenEntityManagerInViewFilter();
registrationBean.setFilter(filter);
registrationBean.setOrder(5);
return registrationBean;
}
}

参考文章:

http://stackoverflow.com/questions/33622222/spring-boot-opensessioninviewfilter-with-actuator-and-custom-security 

http://www.jianshu.com/p/a827ecdda99f

http://www.w_2bc.com/article/201653 /*这个链接博客园不让我发啊,把w_2_b_c中的下划线删了即可*/

错误二:这个在前面也提到过了,返回json数据出现Could not write JSON document: Infinite recursion(无法编写JSON文档:无限递归 );

在后面的使用中发现这个错误也是这样解决的,java.lang.IllegalStateException: Cannot call sendError() after the response has been committed,错误页面到最后就是这样,如图:

解决方法:

  在controller返回数据到统一json转换的时候,出现了json infinite recursion stackoverflowerror的错误,即json在将对象转换为json格式的数据的时候,出现了无限递归调用的情况。
具体的情况如下:
    A类中,有个属性:List<B> b, A与B的关系为 OneToMany;在B类中,有属性A a,引用到A中的字段id,并作为外键。hibernate查询结果正常,可以看到返回的A对象中,有b参数值,但在json转换的时候就出现了无限递归的情况。个人分析,应该是json在序列化A中的b属性的时候,找到了B类,然后序列化B类,而B类中有a属性,因此,为了序列化a属性,json又得去序列化A类,如此递归反复,造成该问题。
解决:
    在B类中a的getter setter方法上加注解@JsonBackReference,其实自己试过只在getter方法上加@JsonBackReference也够了。
 

(转)Springboot+shiro配置笔记+错误小结的更多相关文章

  1. Springboot+shiro配置笔记+错误小结

    软件152 尹以操 springboot不像springmvc,它没有xml配置文件,那该如何配置shiro呢,其实也不难,用java代码+注解来解决这个问题.仅以此篇记录我对shiro的学习,如有对 ...

  2. Springboot+shiro配置笔记+错误小结(转)

    软件152 尹以操 springboot不像springmvc,它没有xml配置文件,那该如何配置shiro呢,其实也不难,用java代码+注解来解决这个问题.仅以此篇记录我对shiro的学习,如有对 ...

  3. springboot shiro配置

    导入相关包(这里配合使用Ehcache缓存) <dependency> <groupId>org.apache.shiro</groupId> <artifa ...

  4. SpringBoot整合Shiro 二:Shiro配置类

    环境搭建见上篇:SpringBoot整合Shiro 一:搭建环境 Shiro配置类配置 shiro的配置主要集中在 ShiroFilterFactoryBean 中 关于权限: anon:无需认证就可 ...

  5. springboot学习笔记:11.springboot+shiro+mysql+mybatis(通用mapper)+freemarker+ztree+layui实现通用的java后台管理系统(权限管理+用户管理+菜单管理)

    一.前言 经过前10篇文章,我们已经可以快速搭建一个springboot的web项目: 今天,我们在上一节基础上继续集成shiro框架,实现一个可以通用的后台管理系统:包括用户管理,角色管理,菜单管理 ...

  6. SpringBoot+Shiro学习(七):Filter过滤器管理

    SpringBoot+Shiro学习(七):Filter过滤器管理 Hiwayz 关注  0.5 2018.09.06 19:09* 字数 1070 阅读 5922评论 1喜欢 20 先从我们写的一个 ...

  7. Shiro学习笔记(5)——web集成

    Web集成 shiro配置文件shiroini 界面 webxml最关键 Servlet 測试 基于 Basic 的拦截器身份验证 Web集成 大多数情况.web项目都会集成spring.shiro在 ...

  8. springboot+shiro

    作者:纯洁的微笑 出处:http://www.ityouknow.com/ 这篇文章我们来学习如何使用Spring Boot集成Apache Shiro.安全应该是互联网公司的一道生命线,几乎任何的公 ...

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

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

随机推荐

  1. LLE局部线性嵌入算法

    非线性降维 流形学习 算法思想有些类似于NLM,但是是进行的降维操作. [转载自] 局部线性嵌入(LLE)原理总结 - yukgwy60648的博客 - CSDN博客 https://blog.csd ...

  2. eclipse不能自动编译XX.java为XX.classs

    问题描述:eclipse不能自动编译XX.java为XX.classs 原因:今天下午写代码,因为需要引入jstl包,引入后发现原来项目中已经引入了,然后我又把包删除了,忘记删除java build ...

  3. C语言中的文本流与二进制流的区别

    近期看到了文本流和二进制流的区别,书上讲的比较含糊,理解不透彻,于是细细琢磨了下,把心得跟大家分享一下: 一.首先回答,什么是文件,流 一个文件通常就是磁盘上的一段命名的存储区.比如 stdio.h ...

  4. Android性能优化的一些方案

    优化Dalvik虚拟机的堆内存分配 1)首先内存方面,可以参考 Android堆内存也可自己定义大小和优化Dalvik虚拟机的堆内存分配 对于Android平台来说,其托管层使用的Dalvik Jav ...

  5. dz数据结构

    pre_common_admincp_cmenu 后台 首页 | 常用操作管理数据表 字段名 数据类型 默认值 允许非空 自动递增 备注 id smallint(6) unsigned    NO 是 ...

  6. 当div没有设置宽度,使用width的fit-content和margin:auto实现元素的水平居中

    当我们做水平居中的时候,会有许多方法,margin:0 auto,或者test-align:center,以及flex布局.当元素的width不固定的时候,我们如何实现水平居中呢,代码如下: < ...

  7. 《转载》struts旅程《2》

    上一篇我们简单了解了struts原理,学习SSH,第一部是傻瓜式学习法,人家怎么做就跟着怎么做就ok.我们以登录为例,Struts配置步骤总结如下(如图2.1): 图2.2 1.          j ...

  8. 【Android】TextView动态设置android:drawableLeft|Right|Top|Bottom,SetColor

    Android中有时需动态设置控件四周的drawble图片,这个时候就需要调用 setCompoundDrawables(left, top, right, bottom),四个参数类型都是drawa ...

  9. javascript飞机大战-----0010完整版代码

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. Spring Boot干货:静态资源和拦截器处理

    前言 本章我们来介绍下SpringBoot对静态资源的支持以及很重要的一个类WebMvcConfigurerAdapter. 正文 前面章节我们也有简单介绍过SpringBoot中对静态资源的默认支持 ...