首先設計用戶權限相關的:
總共有用戶、角色、權限、用戶組、用戶角色關係、用戶用戶組關係、權限關係。
理解:
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的攔截配置這樣子理解。
好了………………