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

  软件152 尹以操

  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也够了。
 
 
3
0
原文地址:https://www.cnblogs.com/hyyq/p/6886004.html

posted @ 2018-01-23 10:31  星朝  阅读(331)  评论(0)    收藏  举报