SSM整合shiro許可權管理
這幾天在學習了shiro許可權管理框架,在剛開始的時候學的時候因為這個配置問題困擾了我很長時間,所以在這篇文章我整合了自己用SSM搭建shiro許可權框架的過程。
1.配置
1.1jar包
在專案配置開始前需要先匯入shiro的相關jar包,下載地址:https://mvnrepository.com/:
如果你的專案時maven專案的話只需要在pom.xml中新增以下幾個依賴:
<!-- shiro核心包 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.3.2</version> </dependency> <!-- 新增shiro web支援 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.3.2</version> </dependency> <!-- 新增shiro spring支援 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency>
其他版本的依賴自行從https://mvnrepository.com/查詢。
1.2web.xml配置
其實shiro許可權控制就是通過攔截器來進行判斷使用者許可權的,因此shiro攔截器的配置跟springMVC的攔截器配置是類似的。
在web.xml檔案中配置shiro的filter攔截器:
<!-- 上下文的位置 --> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <!-- Spring的監聽器 --> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <!-- 新增shiro過濾器 --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <!-- 該值預設為false,表示宣告週期由SpringApplicationContext管理,設定為true表示ServletContainer管理 --> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> <!-- 設定spring容器filter的bean id,如果不設定則找與filter-name一致的bean--> <init-param> <param-name>targetBeanName</param-name> <param-value>shiroFilter</param-value> </init-param> </filter> <!-- POST提交過濾器 UTF-8 --> <filter> <filter-name>encoding</filter-name> <filter-class> org.springframework.web.filter.CharacterEncodingFilter </filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>*.action</url-pattern> </filter-mapping> <!-- 前端控制器 --> <servlet> <servlet-name>shirodemo</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <!-- 此處不配置 預設找 /WEB-INF/[servlet-name]-servlet.xml --> <param-value>classpath:springmvc-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>shirodemo</servlet-name> <!-- 1:*.do *.action 攔截以.do結尾的請求 (不攔截 jsp png jpg .js .css) 2:/ 攔截所有請求 (不攔截.jsp) 建議使用此種 方式 (攔截 .js.css .png) (放行靜態資源) 3:/* 攔截所有請求(包括.jsp) 此種方式 不建議使用 --> <url-pattern>*.action</url-pattern> </servlet-mapping>
這裡需要注意的一點是targetBeanName下面的shiroFilter這個value要記牢,後面在配置shiro和spring的時候會用到。
1.3配置applicationContext.xml
類似springmvc一樣,需要寫一個配置檔案來配置攔截器。
<!--自定義Realm,注入認證憑證器--> <bean id="userRealm" class="cn.lijiahao.demo.realm.UserRealm"> <property name="credentialsMatcher" ref="credentialsMatcher"/> </bean> <!--securityManager管理--> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="userRealm"></property> </bean> <!--shiro 過濾器--> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- Shiro過濾器的核心安全介面,這個屬性是必須的--> <property name="securityManager" ref="securityManager"/> <!--身份認證失敗,則跳轉到登入頁面的配置--> <property name="loginUrl" value="/login.action"/> <property name="successUrl" value="/index.action"></property> <!--許可權認證失敗,則跳轉到指定頁面--> <property name="unauthorizedUrl" value="/unauthorized.action"/> <!-- Shiro連線約束配置,即過濾鏈的定義--> <property name="filterChainDefinitions"> <value> /index.action=anon /index.html=anon /logout.action=logout /register.action=anon /login.jsp=anon /test.action=authc /assets/**=anon /admin/**=roles[admin] /user/**=roles[user] /**=authc </value> </property> </bean>
上面的id="shiroFilder"就是剛剛在web.xml裡的argetBeanName下面的shiroFilter的value,這裡必須要保持一致。
shiro的過濾攔截器需要載入一個securityManager安全管理器,這個必須要有的。
<!--securityManager管理-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm"></property><!--注入自定義realm,若果沒有自定義realm,可以不注入-->
</bean>
定義攔截的內容在value標籤裡:
<!-- Shiro連線約束配置,即過濾鏈的定義-->
<property name="filterChainDefinitions">
<value>
/index.action=anon<!-- 定義/index.action可以匿名訪問-->
/index.html=anon
/logout.action=logout
/register.action=anon
/login.jsp=anon
/test.action=authc
/assets/**=anon
/admin/**=roles[admin]
/user/**=roles[user]
/**=authc
</value>
</property>
shiro的過濾器總結如下:
過濾器簡稱
對應的java類
anon
org.apache.shiro.web.filter.authc.AnonymousFilter
authc
org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic
org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
perms
org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port
org.apache.shiro.web.filter.authz.PortFilter
rest
org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles
org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl
org.apache.shiro.web.filter.authz.SslFilter
user
org.apache.shiro.web.filter.authc.UserFilter
logout
org.apache.shiro.web.filter.authc.LogoutFilter
anon:例子/admins/**=anon 沒有引數,表示可以匿名使用。
authc:例如/admins/user/**=authc表示需要認證(登入)才能使用,FormAuthenticationFilter是表單認證,沒有引數
roles:例子/admins/user/**=roles[admin],引數可以寫多個,多個時必須加上引號,並且引數之間用逗號分割,當有多個引數時,例如admins/user/**=roles["admin,guest"],每個引數通過才算通過,相當於hasAllRoles()方法。
perms:例子/admins/user/**=perms[user:add:*],引數可以寫多個,多個時必須加上引號,並且引數之間用逗號分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],當有多個引數時必須每個引數都通過才通過,想當於isPermitedAll()方法。
rest:例子/admins/user/**=rest[user],根據請求的方法,相當於/admins/user/**=perms[user:method] ,其中method為post,get,delete等。
port:例子/admins/user/**=port[8081],當請求的url的埠不是8081是跳轉到schemal://serverName:8081?queryString,其中schmal是協議http或https等,serverName是你訪問的host,8081是url配置裡port的埠,queryString
是你訪問的url裡的?後面的引數。
authcBasic:例如/admins/user/**=authcBasic沒有引數表示httpBasic認證
ssl:例子/admins/user/**=ssl沒有引數,表示安全的url請求,協議為https
user:例如/admins/user/**=user沒有引數表示必須存在使用者, 身份認證通過或通過記住我認證通過的可以訪問,當登入操作時不做檢查
注:
anon,authcBasic,auchc,user是認證過濾器,
perms,roles,ssl,rest,port是授權過濾器
以上是配置過程
2.shiro認證
上面是我在用shiro的許可權管理時的配置過程,專案配置過程要根據具體的專案來,可能會有點細節不同,但是總體不變,遇到不會的多去百度百度,因為是第一次接觸shiro,所以我當時就一個配置過程就用了三天,所以不要急,遇到不會的多上網查查。
當你攔截器設定好了,可以成功攔截使用者的操作,然後我們需要對使用者進行許可權驗證。所以我們需要繼承shiro的AuthorizingRealm攔截器,重寫兩個方法。
- 重寫doGetAuthenticationInfo方法是:登入驗證,當需要登入的時候,就會呼叫該方法進行驗證。
- 重寫doGetAuthorizationInfo方法:這個是授權驗證,與上面的過濾器相結合。
思路如下:
- 登入驗證: 根據賬號從資料庫獲取賬號密碼進行比較,如果一致則登入成功,就會儲存到,否則登入失敗
- 授權驗證:在登入成功後,根據使用者id獲取到該使用者的許可權,並把許可權儲存在安全管理器之中,當用戶訪問的時候,會從管理器中判斷該使用者是否有許可權去訪問該url。
我這裡只寫了認證過程:
2.1自定義realm
public class UserRealm extends AuthorizingRealm{
@Autowired
private UserService userService;
@Override
public String getName() {
return "userRealm";
}
// 支援什麼型別的token
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof UsernamePasswordToken;
}
/**
*
*@Description 自定義授權方法
*@param
*@author 李佳浩
*@Date 2018年10月14日 下午12:16:34
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection token) {
// TODO Auto-generated method stub
System.out.println("111");
return null;
}
/**
*
*@Description 自定義的認證方法
*@param
*@author 李佳浩
*@Date 2018年10月14日 下午12:15:57
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//從token中獲取使用者身份資訊
String username = (String)token.getPrincipal();
System.out.println("principal:"+username);
//從資料庫中查詢username
User user = userService.selectByUsername(username);
//如果查詢不到則返回null
if(user==null) return null;
String password = user.getPassword();
//String salt = user.getSalt();//鹽
// 返回認證資訊由父類AuthenticatingRealm進行認證
//SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user, password,ByteSource.Util.bytes(salt), getName());
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user, password, getName());
return simpleAuthenticationInfo;
}
}
2.2controller層
/**
* @Description 使用者登入
* @return
* @author 李佳浩
* @Date 2018年10月13日 下午6:39:01
*/
@RequestMapping("/login.action")
public String login(HttpServletRequest request)throws Exception{
// shiro在認證過程中出現錯誤後將異常類路徑通過request返回
String exceptionClassName = (String) request.getParameter("shiroLoginFailure");
if(exceptionClassName!=null){
if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
throw new CacheException("賬號不存在");
} else if (IncorrectCredentialsException.class.getName().equals(
exceptionClassName)) {
throw new CacheException("使用者名稱/密碼錯誤");
} else{
throw new Exception();//最終在異常處理器生成未知錯誤
}
}
//認證失敗會回到login.jsp
return "login";
}
2.3dao層
我的專案用的是mybatis
程式碼如下:
public interface UserDao {
User selectByid(@Param("id")String id);
User selectByUsername(@Param("username")String username);
List<User> selectAll();
List<User> selectAllOrderPag(@Param("begin")int begin,@Param("size")int size);
int selectCountOfRows();
int add(User user);
int deleteById(@Param("id")String id);
int update(User user);
}
UserDao.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org/DTD Mapper 3.0" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.lijiahao.demo.dao.UserDao">
<!-- 通過使用者名稱查詢使用者 -->
<select id="selectByUsername" parameterType="String" resultType="user">
select *
from sys_user
where username = #{username}
</select>
<!-- 通過id查詢使用者 -->
<select id="selectById" parameterType="Integer" resultType="user">
select *
from sys_user
where id = #{id}
</select>
<!-- 新增使用者 -->
<insert id="add" parameterType="user">
insert into sys_user(id,username,password,name,dataOfBirth,age,gender)
values(#{id},#{username},#{password},#{name},#{dataOfBirth},#{age},#{gender})
</insert>
<!-- 查詢所有使用者 -->
<select id="selectAll" resultType="user">
select * from sys_user
</select>
<!-- 根據begin,size來返回使用者list列表 -->
<select id="selectAllOrderPage" parameterType="Integer" resultType="user">
select * from sys_user limit #{begin},#{size}
</select>
<!-- 返回所有資訊的行數 -->
<select id="selectCountOfRows" resultType="Integer">
select count(*) from sys_user
</select>
<!-- 更新使用者資訊 -->
<update id="update" parameterType="user">
update sys_user
<set>
<if test="username !=null and username !=''">
username=#{username},
</if>
<if test="password !=null and password !=''">
password=#{password},
</if>
<if test="dataOfBirth !=null">
dataOfBirth=#{dataOfBirth},
</if>
<if test="name !=null and name !=''">
name=#{name},
</if>
<if test="age !=null and age !=''">
age=#{age},
</if>
<if test="gender !=null and gender !=''">
gender=#{gender},
</if>
</set>
where id=#{id}
</update>
<!-- 通過id刪除使用者 -->
<delete id="deleteById" parameterType="Integer">
delete from sys_user where id = #{id}
</delete>
</mapper>
2.4service層
service層就省略了,service層就是呼叫dao層實現。
2.5pojo層
public class User implements Serializable{
private String id;
private String username;
private String password;
private String name;
private Timestamp dataOfBirth;//出生日期
private int age;
private String gender;
private String salt;//鹽
private String locked;
//省略set,get和構造方法方法
}
2.6資料庫
資料庫參照這篇文章https://blog.csdn.net/hzw2312/article/details/54612962
資料庫結構現在地址:https://download.csdn.net/download/codehaohao/10722315
文章參考文章: