关于springcloud使用shiro的权限控制
Posted Mr. Dreamer Z
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于springcloud使用shiro的权限控制相关的知识,希望对你有一定的参考价值。
之前我在网上找了很久的使用基于springcloud用shiro来作为权限控制。但是都没有一个好的demo,所以自己查了资料然后问了一些人,写了一个简单的demo。好了,话不多少直接进入正题
由于是使用springcloud,那么关于springcloud的一些知识我在这就不一一去讲了。直接讲一些shiro关键的地方。不懂的朋友可以去看看这位大牛写的帖子https://www.fangzhipeng.com/archive/?tag=SpringCloud 真的写的很好,springcloud快速入门
这里说一下我的环境配置:JDK11,TOMCAT9
首先我们需要创建一个server项目,然后创建一个zuul项目(由于现在很多服务架构都会用到负载均衡,所以我没有把权限和zuul放在一起,而是由zuul转发)。
OK,创建好这两个项目之后,我们开始搭建权限项目。
在pom中引入这两个对应的jar包
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
然后去创建关于shiro的配置文件
这两个类在网上都可以搜到,千篇一律的配置,只是根据个人不同的需求去更改其中过滤的位置。
首先创建一个配置文件,开头使用@Configuration去注解,让其成为一个配置文件。这边说明一下,由于是使用分布式,而且我这边没有使用包含的页面,所以没有去设置任何界面有关的权限配置。
@Configuration
public class ShiroConfig
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager)
System.out.println("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//拦截器.
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
// 配置不会被拦截的链接 顺序判断
//filterChainDefinitionMap.put("/static/**", "anon");
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
//<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
//<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
//filterChainDefinitionMap.put("/userInfo/**", "authc");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
//shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
//shiroFilterFactoryBean.setSuccessUrl("/index");
//未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/response/responseMsg");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
@Bean
public MyShiroRealm myShiroRealm()
MyShiroRealm myShiroRealm = new MyShiroRealm();
return myShiroRealm;
@Bean
public SecurityManager securityManager()
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
return securityManager;
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager)
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
@Bean(name="simpleMappingExceptionResolver")
public SimpleMappingExceptionResolver
createSimpleMappingExceptionResolver()
SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
Properties mappings = new Properties();
mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理
mappings.setProperty("UnauthorizedException","/response/responseMsg");//无权限跳入的方法
r.setExceptionMappings(mappings); // None by default
r.setDefaultErrorView("error"); // No default
r.setExceptionAttribute("ex"); // Default is "exception"
//r.setWarnLogCategory("example.MvcLogger"); // No default
return r;
然后创建一个针对于登陆和权限验证的类,说明一下,我这边使用的fegin,数据库也没有在这个权限项目,是去调用的其他服务的接口。当然了,密码验证这个段代码,可以根据自己的需求去改写。使用MD5也好,不要盐也好。我这边没有写的那么复杂
public class MyShiroRealm extends AuthorizingRealm
@Autowired
private UserService userService;
/**
* 角色
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)
System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
UserInfo userInfo = (UserInfo)principals.getPrimaryPrincipal();
for(SysRole role:userInfo.getRoleList())
authorizationInfo.addRole(role.getRole());
for(SysPermission p:role.getPermissions())
authorizationInfo.addStringPermission(p.getPermission());
return authorizationInfo;
/*主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException
System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
//获取用户的输入的账号.
String username = (String)token.getPrincipal();
System.out.println(token.getCredentials());
//通过username从数据库中查找 User对象,如果找到,没找到.
//实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
UserInfo userInfo = userService.findByUsername(username);
System.out.println("----->>userInfo="+userInfo);
if(userInfo == null)
return null;
if(!userInfo.getPassword().equals(new String((char[])token.getCredentials())))
throw new IncorrectCredentialsException("密码错误--------------"); //如果密码错误
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
userInfo, //用户名
userInfo.getPassword(), //密码
ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt
this.getName() //realm name
);
return authenticationInfo;
这是我的业务层的代码,是对应我另一个项目的方法
这个是我登陆的接口,由权限这个项目直接去验证。好处是因为由于加入权限这个要求的话,所有的接口都需要从权限这个项目进行发送。
@RequestMapping("/login")
public UserInfo login(HttpServletRequest request, Map<String, Object> map, String username, String password) throws Exception
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username,
password);
String error = null;
UserInfo userInfo = null;
try
subject.login(token);
userInfo = (UserInfo) SecurityUtils.getSubject().getPrincipal();
//token.setRememberMe(true);
catch (UnknownAccountException e)
error = "用户名/密码错误";
catch (IncorrectCredentialsException e)
error = "用户名/密码错误";
catch (ExcessiveAttemptsException e)
// TODO: handle exception
error = "登录失败多次,账户锁定10分钟";
catch (AuthenticationException e)
// 其他错误,比如锁定,如果想单独处理请单独catch处理
error = "其他错误:" + e.getMessage();
if (error != null) // 出错了,返回登录页面
//request.setAttribute("error", error);
return null;
if(userInfo!=null)
return userInfo;
return null;
执行到subject.login 这句话之后,会直接进入我们之前写的密码验证那个方法。
这个是无权限验证之后,会进入的方法
@RestController
@RequestMapping(value = "/response")
public class ResponseController
@RequestMapping(value = "/responseMsg")
public String responseMsg()
return "failure";
这个是user项目的实体类
@Entity
@Table(name = "userinfo")
public class UserInfo implements Serializable
@Id
@GeneratedValue
private Integer uid;
@Column(unique =true)
private String username;//帐号
private String name;//名称(昵称或者真实姓名,不同系统不同定义)
private String password; //密码;
private String salt;//加密密码的盐
private byte state;//用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 , 1:正常状态,2:用户被锁定.
@ManyToMany(fetch= FetchType.EAGER)//立即从数据库中进行加载数据;
@JoinTable(name = "SysUserRole", joinColumns = @JoinColumn(name = "uid") , inverseJoinColumns =@JoinColumn(name = "roleId") )
private List<SysRole> roleList;// 一个用户具有多个角色
public UserInfo()
public Integer getUid()
return uid;
public void setUid(Integer uid)
this.uid = uid;
public String getUsername()
return username;
public void setUsername(String username)
this.username = username;
public String getName()
return name;
public void setName(String name)
this.name = name;
public String getPassword()
return password;
public void setPassword(String password)
this.password = password;
public String getSalt()
return salt;
public void setSalt(String salt)
this.salt = salt;
public byte getState()
return state;
public void setState(byte state)
this.state = state;
public List<SysRole> getRoleList()
return roleList;
public void setRoleList(List<SysRole> roleList)
this.roleList = roleList;
/**
* 密码盐.
* @return
*/
public String getCredentialsSalt()
return this.username+this.salt;
//重新对盐重新进行了定义,用户名+salt,这样就更加不容易被破解
@Override
public String toString()
return "UserInfo" +
"uid=" + uid +
", username='" + username + '\\'' +
", name='" + name + '\\'' +
", password='" + password + '\\'' +
", salt='" + salt + '\\'' +
", state=" + state +
'';
@Entity
@Table(name = "sysrole")
public class SysRole
@Id
@GeneratedValue
private Integer id; // 编号
private String role; // 角色标识程序中判断使用,如"admin",这个是唯一的:
private String description; // 角色描述,UI界面显示使用
private Boolean available = Boolean.FALSE; // 是否可用,如果不可用将不会添加给用户
//角色 -- 权限关系:多对多关系;
@ManyToMany(fetch= FetchType.EAGER)
@JoinTable(name="SysRolePermission",joinColumns=@JoinColumn(name="roleId"),inverseJoinColumns=@JoinColumn(name="permissionId"))
private List<SysPermission> permissions;
public Integer getId()
return id;
public void setId(Integer id)
this.id = id;
public String getRole()
return role;
public void setRole(String role)
this.role = role;
public String getDescription()
return description;
public void setDescription(String description)
this.description = description;
public Boolean getAvailable()
return available;
public void setAvailable(Boolean available)
this.available = available;
public List<SysPermission> getPermissions()
return permissions;
public void setPermissions(List<SysPermission> permissions)
this.permissions = permissions;
@Entity
@Table(name = "syspermission")
public class SysPermission implements Serializable
@Id
@GeneratedValue
private Integer id;//主键.
private String name;//名称.
@Column(columnDefinition="enum('menu','button')")
private String resourceType;//资源类型,[menu|button]
private String url;//资源路径.
private String permission; //权限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view
private Long parentId; //父编号
private String parentIds; //父编号列表
private Boolean available = Boolean.FALSE;
public Integer getId()
return id;
public void setId(Integer id)
this.id = id;
public String getName()
return name;
public void setName(String name)
this.name = name;
public String getResourceType()
return resourceType;
public void setResourceType(String resourceType)
this.resourceType = resourceType;
public String getUrl()
return url;
public void setUrl(String url)
this.url = url;
public String getPermission()
return permission;
public void setPermission(String permission)
this.permission = permission;
public Long getParentId()
return parentId;
public void setParentId(Long parentId)
this.parentId = parentId;
public String getParentIds()
return parentIds;
public void setParentIds(String parentIds)
this.parentIds = parentIds;
public Boolean getAvailable()
return available;
public void setAvailable(Boolean available)
this.available = available;
这里都是多对多的关系,这里建议使用单向多对多。不然从数据取出值的时候会报出内存溢出的异常,网上说是由于双向关联的关系造成的,所以我把改成了单向的了。由于我在权限项目调用了user项目的方法,所以这里需要去写一个通过用户名查询的语句。这个简单的hql语句我就不贴出来了。
这里有个重点的地方是权限项目必须也拥有user项目的实体bean类。只需要单纯的类即可。
ok 我们试一下 这个我的几个项目
zuul转发,security权限控制,user用户
登陆
说一下我报这个错的问题,是由于userinfo这个多对多的原因。zuul转发之后,userinfo成功发送回来。但是无法转换格式,所以报错了。我之前是想通过通过和token的键值对形式传入redis中(token和userinfo)。但是多对多关系的类好像不行。我准备换成一对多形式。如果哪位朋友有解决方案也可以告诉我一下,在这谢谢了。
你们可以忽略这个问题,直接zuul转发即可,不需要去做其他操作。
这个是我的权限控制。我这个账户只有 userInfo:add 权限 ,没有 userInfo:del 权限
看下结果
ok成功了,这里提一点。由于分布式的原因,zuul每次转发的seesion都不一致,所以会和shiro的机制冲突。导致shiro无法去执行操作。需要在zuul配置文件中加一句
zuul.routes.oaUser.sensitiveHeaders="*" //会过滤客户端请求中的和该配置项匹配的headers
demo地址如下:
springcloud-shiro_zuul整合shiro-Java代码类资源-CSDN下载
以上是关于关于springcloud使用shiro的权限控制的主要内容,如果未能解决你的问题,请参考以下文章
SpringBoot项目+Shiro(权限框架)+Redis(缓存)集成
springcloud vue.js 前后分离 微服务 分布式 activiti工作流 集成代码生成器 shiro权限
springcloud vue.js 微服务分布式 前后分离 集成代码生成器 shiro权限 activiti工作流
springcloud vue.js 微服务分布式 activiti工作流 前后分离 集成代码生成器 shiro权限