首先设计用户权限相关的:
总共有用户、角色、权限、用户组、用户角色关系、用户用户组关系、权限关系。
理解:
1.一个用户可以有多个角色,一个角色可以被多个用户拥有,多对多关系拆分到用户角色关系。
2.一个用户可以属于多个用户组,一个用户组可以有多个用户,多对多关系拆分到用户用户组关系。
3.一个权限可以有多个用户、角色或者用户组,一个用户、角色或者用户组可以有多个权限,拆分到权限关系。
如图(请忽略作图水平,用画图画的……):
算了,不多说了,附上表的sql:
-- 权限表
DROP TABLE IF EXISTS `fms_permission`;
CREATE TABLE IF NOT EXISTS `fms_permission` (
`id` varchar(64) NOT NULL,
`permission_name` varchar(1000) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 角色、用户和用户组与权限的关系
DROP TABLE IF EXISTS `fms_permission_relation`;
CREATE TABLE IF NOT EXISTS `fms_permission_relation` (
`id` varchar(64) NOT NULL,
`permission_id` varchar(64) NOT NULL,
`username` varchar(40) DEFAULT NULL,
`role_id` varchar(64) DEFAULT NULL,
`group_id` varchar(64) DEFAULT NULL,
`permission_type` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 角色表
DROP TABLE IF EXISTS `fms_role`;
CREATE TABLE IF NOT EXISTS `fms_role` (
`id` varchar(64) NOT NULL,
`role_name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 用户角色关系表
DROP TABLE IF EXISTS `fms_role_relation`;
CREATE TABLE IF NOT EXISTS `fms_role_relation` (
`id` varchar(64) NOT NULL,
`username` varchar(40) NOT NULL,
`role_id` varchar(64) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 用户表
DROP TABLE IF EXISTS `fms_user`;
CREATE TABLE IF NOT EXISTS `fms_user` (
`username` varchar(40) NOT NULL,
`nick_name` varchar(50) NOT NULL,
`password` varchar(255) NOT NULL,
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 用户组表
DROP TABLE IF EXISTS `fms_user_group`;
CREATE TABLE IF NOT EXISTS `fms_user_group` (
`id` varchar(64) NOT NULL,
`group_name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 用户用户组关系表
DROP TABLE IF EXISTS `fms_user_group_relation`;
CREATE TABLE IF NOT EXISTS `fms_user_group_relation` (
`id` varchar(64) NOT NULL,
`group_id` varchar(64) NOT NULL,
`username` varchar(40) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
mybatis的一些映射就不上了,后面会有项目的的地址,权限是公共的可以去看一下。写的比较差,有啥意见啥的还请指点。
好了,不多说了,接下来就是shiro的,首先把maven要的依赖贴上:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
这里踩过一个坑,就开始我是使用maven repository里面shiro最新的包:1.4.0版本的,后面启动项目直接tomcat报错了,不知道是不是1.4.0的配置有什么改动或者我配置有哪里不正确,报错原因是jar包存在冲突或者jar包没有下载好,但是我用maven test能跑过,期待大佬指点。
接下来就是shiro的配置了,首先先把web.xml要添加的内容贴上:
这里的filter-name等会shiro的配置文件是要使用到的。
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
spring-shiro.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/mvc/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/mvc/spring-aop.xsd">
<!-- 权限配置管理器 -->
<bean id="securityManager"
class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- ref对应我们写的realm MyShiro -->
<property name="realm" ref="myShiro" />
<!-- 使用下面配置的缓存管理器 -->
<property name="cacheManager" ref="cacheManager" />
</bean>
<!-- 配置shiro的过滤器工厂, id- shiroFilter要和我们在web.xml中配置的过滤器一致 -->
<bean id="shiroFilter"
class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 调用我们配置的权限管理器 -->
<property name="securityManager" ref="securityManager" />
<!-- 配置我们的登录请求地址 -->
<property name="loginUrl" value="/login.jsp" />
<!-- 配置我们在登录成功后的跳转地址,如果你访问的是非/login地址,则跳转到你访问的地址,不配置默认跳回上一个访问的url -->
<!-- <property name="successUrl" value="/index" /> -->
<!-- 如果您请求的资源不再您的权限范围,则跳转到/403请求地址 -->
<property name="unauthorizedUrl" value="/403" />
<!-- 权限配置 -->
<property name="filterChainDefinitions">
<value>
<!-- anon表示此地址不需要任何权限即可访问(静态资源) -->
/images/**=anon
/js/**=anon
/css/**=anon
/bootstrap/css/**=anon
/bootstrap/fonts/**=anon
/bootstrap/js/**=anon
<!-- 登录请求不拦截 -->
/user/login=anon
/login.jsp=anon
<!-- perms[index_view]表示访问此连接需要权限为index_view的用户 -->
/index.jsp=perms[index_view]
<!-- roles[manager]表示访问此连接需要用户的角色为manager -->
<!-- /user/add=roles[manager] /user/del/**=roles[admin] /user/edit/**=roles[manager] -->
<!-- 注销 -->
/logout=logout
<!--所有的请求(除去配置的静态资源请求或请求地址为anon的请求)都要通过登录验证,如果未登录则跳到/login -->
/** = authc
</value>
</property>
</bean>
<bean id="cacheManager"
class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />
<bean id="lifecycleBeanPostProcessor"
class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
</beans>
perms[index_view]这里面的index_view是自己定义的权限名称,后面写用户认证的时候看代码就清楚了。
anon代表不拦截,还有些其他的,如logout表示注销用户,shiro会把登录的信息清理掉,其他的可以自己查阅一下。另外还可以自己设置第三方缓存,我这里用的是自带的。
这个需要加入到applicationContext.xml里面的,要在spring监听起来的时候加载的。
<import resource="classpath:pring-shiro.xml"/>
我这里使用的是引入,也可以定义好差不多的后缀,在web.xml那里配置如applicaitonContext-*.xml这样子。
接下来就是 用户认证和登录了:
MyShiro:
package com.xqtion.fms.service.impl;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import com.xqtion.fms.dao.IUserDao;
import com.xqtion.fms.entity.PermissionRelation;
import com.xqtion.fms.entity.RoleRelation;
import com.xqtion.fms.entity.User;
import com.xqtion.fms.entity.UserGroupRelation;
import com.xqtion.fms.service.IMyShiro;
@Service
@Transactional
public class MyShiro extends AuthorizingRealm implements IMyShiro {
@Autowired
private IUserDao userDao;
/**
* 权限认证
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 获取登录时输入的用户名
String loginName = (String) principalCollection.fromRealm(getName()).iterator().next();
// 去数据库查询用户
User user = userDao.getUserByUserName(loginName);
if (null != user) {
// 权限信息对象info,用来存放查出用户的所有角色(role)及权限(permission)
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 用户的角色集合
Set<String> roles = new HashSet<>();
// 用户权限的集合
Set<String> permissions = new HashSet<>();
// 获取用户角色
getUserRoleName(user.getRoleRelations(), roles);
// 获取用户权限
getUserPermission(user, permissions);
// 添加角色
if (!CollectionUtils.isEmpty(roles)) {
info.setRoles(roles);
}
// 添加权限
if (!CollectionUtils.isEmpty(permissions)) {
info.addStringPermissions(permissions);
}
return info;
}
return null;
}
/**
* 登录验证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
throws AuthenticationException {
// UsernamePasswordToken对象用来存放提交的登录信息
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
// 查出是否有此用户
User user = userDao.getUserByUserName(token.getUsername());
if (null != user) {
// 若存在,将此用户放到登录认证的info中
return new SimpleAuthenticationInfo(user.getUsername(),
user.getPassword(), getName());
}
return null;
}
// 获取用户权限
private void getUserRoleName(final List<RoleRelation> roleRelations, final Set<String> roles) {
if (!CollectionUtils.isEmpty(roleRelations)) {
for (RoleRelation roleRelation : roleRelations) {
roles.add(roleRelation.getRole().getRoleName());
}
}
}
// 根据用户本身、角色、用户组获得权限
private void getUserPermission(final User user, final Set<String> permissions) {
// 取消用户权限的集合
Set<String> downPermissions = new HashSet<>();
// 根据用户
getUserPermission(user.getPermissionRelations(), permissions, downPermissions);
// 根据角色
if (!CollectionUtils.isEmpty(user.getRoleRelations())) {
for (RoleRelation roleRelation : user.getRoleRelations()) {
getUserPermission(roleRelation.getRole().getPermissionRelations(), permissions, downPermissions);
}
}
// 根据用户组
if (!CollectionUtils.isEmpty(user.getUserGroupRelations())) {
for (UserGroupRelation groupRelation : user.getUserGroupRelations()) {
getUserPermission(groupRelation.getUserGroup().getPermissionRelations(), permissions, downPermissions);
}
}
// 移除取消的权限
permissions.removeAll(downPermissions);
}
// 根据权限关系获取权限
private void getUserPermission(final List<PermissionRelation> permissionRelations, final Set<String> permissions,
final Set<String> downPermissions) {
if (!CollectionUtils.isEmpty(permissionRelations)) {
for (PermissionRelation permissionRelation : permissionRelations) {
if (Objects.equals(permissionRelation.getPermissionType(), "up")) {
permissions.add(permissionRelation.getPermission().getPermissionName());
} else {
downPermissions.add(permissionRelation.getPermission().getPermissionName());
}
}
}
}
}
这个是根据上面设计的用户权限来实现的,有自己的需求或者想法可以灵活改动,反正就是把角色和权限加进去,验证 用户登录还可以用加密,这里暂时没有加密。
还有controller:
UserController:
package com.xqtion.fms.controller;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.xqtion.fms.entity.User;
import com.xqtion.fms.service.IUserService;
@Controller
@RequestMapping(value = "/user")
public class UserController {
@Resource
private IUserService userService;
@RequestMapping(value="/login2", method=RequestMethod.POST)
public ModelAndView login2(User model, HttpSession session) {
User user = userService.login(model);
if (user == null || !user.getPassword().equals(model.getPassword())) {
return new ModelAndView("redirect:/login.jsp");
} else {
session.setAttribute("user", user);
ModelAndView mav = new ModelAndView();
mav.setViewName("redirect:/index.jsp");
return mav;
}
}
@RequestMapping(value="/login", method=RequestMethod.POST)
public String login3(User user, HttpServletRequest request,BindingResult bindingResult, RedirectAttributes redirectAttributes) {
try {
if (bindingResult.hasErrors()) {
return "redirect:/login.jsp";
}
// 使用权限工具进行用户登录,登录成功后跳到shiro配置的successUrl中,与下面的return没什么关系!
Subject subject = SecurityUtils.getSubject();
subject.login(new UsernamePasswordToken(user.getUsername(), user.getPassword()));
return "redirect:/" + resolveUrl(WebUtils.getAndClearSavedRequest(request).getRequestUrl());
} catch (AuthenticationException e) {
redirectAttributes.addFlashAttribute("message", "用户名或密码错误");
return "redirect:/login.jsp";
}
}
// 解析url
private String resolveUrl(String url) {
String pattern = "/fms/";
String redirectUrl = url.substring(pattern.length(), url.length());
System.out.println("============" + redirectUrl);
return redirectUrl;
}
}
这里又有个坑,上面的shiro配置文件不是说,successUrl没有配置的话会自动跳转回上一个页面,亲测无效,不知道是不是漏了配置些什么,再次跪求大佬指点……我这里有点取巧,感觉不太好吧……,是自己拿到shiro存在自己session里面的上一个url,然后自己解出需要的部分去进行重定向的……
好了,讲到这里差不多了,肯定还有很多没讲明白,需要项目源码的可以到我的码云项目地址去看:项目地址
目前写的东西还是很少,后续有时间的话会不断地增加一些新的东西去完善的……最后,再次跪求大佬指点给点建议……
自己总结下:之前使用shiro有些地方一直跟springmvc配置的视图渲染搞不清,现在搞清楚了,只有controller里面返回的才会去触发到springmvc的视图渲染器。直接url访问webapps下面的资源是不影响的,shiro的拦截配置这样子理解。
好了………………