記錄一些spring-shiro的問題

首先設計用戶權限相關的:

總共有用戶、角色、權限、用戶組、用戶角色關係、用戶用戶組關係、權限關係。

理解:

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的攔截配置這樣子理解。


好了………………

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章