【Shiro】Shiro與web集成
與web項⽬集成後,shiro的⼯作模式如下:
如上:ShiroFilter攔截所有請求,對於請求做訪問控制
如請求對應的功能是否需要有 認證的身份,是否需要某種⻆⾊,是否需要某種權限等。
- 如果沒有做 身份認證,則將請求強制跳轉到登錄⻚⾯。
如果沒有充分的⻆⾊或權限,則將請求跳轉到權限不⾜的⻚⾯。 - 如果校驗成功,則執⾏請求的業務邏輯
1. pom依賴
<!-- ============ Servlet ============ -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- ============== SpringMVC ============== -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
<!-- ============ shiro ============ -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.0</version>
</dependency>
<!-- ============ log ============ -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.12</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.12</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
2. 基礎代碼
package com.siyi.controller;
import com.siyi.pojo.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/user")
public class UserController {
@GetMapping("/login")
public String login(){
System.out.println("goto login page");
return "login";
}
@PostMapping("/login")
public String loginLogic(User user){
System.out.println("login logic");
//獲取subject 調用login
Subject subject = SecurityUtils.getSubject();
//創建用於登錄的令牌
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
//登錄失敗會拋出異常,則交由異常解析器處理
subject.login(token);
return "index";
}
@GetMapping("/all")
public String queryAllUsers(){
System.out.println("query all users");
return "index";
}
@RequestMapping("/perms/error")
public String permsError(){
System.out.println("權限不足");
return "perm_error";
}
}
3. 配置
3.1 web.xml
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 接收所有請求,以通過請求路徑 識別是否需要 安全校驗,如果需要則觸發安全校驗
做訪問校驗時,會遍歷過濾器鏈。(鏈中包含shiro.ini中urls內使⽤的過濾器)
會通過ThreadContext在當前線程中綁定⼀個subject和SecurityManager,供請求內使⽤
可以通過SecurityUtils.getSubject()獲得Subject
1.在項目的最外層構建訪問控制層
2.在啓動時,初始化shiro環境
-->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 在項⽬啓動時,加載web-info 或 classpath下的 shiro.ini ,並構建WebSecurityManager。
構建所有配置中使⽤的過濾器鏈(anon,authc等),ShiroFilter會獲取此過濾器鏈
-->
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<!-- ⾃定義ini⽂件名稱和位置,默認會取尋找classpath下的 shiro.ini 。
<context-param>
<param-name>shiroConfigLocations</param-name>
<param-value>classpath:shiro9.ini</param-value>
</context-param>
-->
<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>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
3.2 shiro.ini
[users]
zhangsan=123,admin
lisi=456,manager,seller
wangwu=789,clerk
[roles]
admin=*
clerk=user:query,user:detail:query
manager=user:*
[main]
#沒有身份認證時,跳轉地址
shiro.loginUrl = /user/login
#⻆⾊或權限校驗不通過時,跳轉地址
shiro.unauthorizedUrl=/user/perms/error
#登出後的跳轉地址,回⾸⻚
shiro.redirectUrl=/
[urls]
# 如下格式:"訪問路徑 = 過濾器"
#【1.ant路徑:? * ** 細節如下】
# /user/login/page , /user/login/logic 是普通路徑
# /user/* 代表/user後還有⼀級任意路徑 : /user/a , /user/b , /user/c , /user/xxxxxxxxxxx
# /user/** 代表/user後還有任意多級任意路徑: /user/a , /user/a/b/c , /user/xxxx/xxxxxx/xxxxx
# /user/hello? 代表hello後還有⼀個任意字符: /user/helloa , /user/hellob , /user/hellox
#【2.過濾器,細節如下】
# anon => 不需要身份認證
# authc => 指定路徑的訪問,會驗證是否已經認證身份,如果沒有則會強制轉發到 最上⾯配置的loginUrl上
# ( ops:登錄邏輯本身千萬不要被認證攔截,否則⽆法登錄 )
# logout => 訪問指定的路徑,可以登出,不⽤定義handler。
# roles["manager","seller"] => 指定路徑的訪問需要subject有這兩個⻆⾊
# perms["user:update","user:delete"] => 指定路徑的訪問需要subject有這兩個權限
/user/login/page = anon
/user/login/logic = anon
/user/query = authc
/user/update = authc,roles["manager","seller"]
/user/delete = authc, perms["user:update","user:delete"]
/user/logout = logout # 內置的過濾器,直接登出
#其餘路徑都需要身份認證【⽤此路徑需謹慎】
/** = authc
#【3.注意】
# 以上的就是過濾器鏈條
# url的匹配,是從上到下匹配,⼀旦找到可以匹配的則停⽌,所以,通配範圍⼤的url要往後放,
# 如"/user/delete" 和 "/user/**"
3.3 其他默認過濾器
過濾器的名稱實際上是一個枚舉類型:每個名稱對應一個過濾器。
package org.apache.shiro.web.filter.mgt;
import org.apache.shiro.util.ClassUtils;
import org.apache.shiro.web.filter.authc.*;
import org.apache.shiro.web.filter.authz.*;
import org.apache.shiro.web.filter.session.NoSessionCreationFilter;
import javax.servlet.Filter;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import java.util.LinkedHashMap;
import java.util.Map;
public enum DefaultFilter {
anon(AnonymousFilter.class),
authc(FormAuthenticationFilter.class),
authcBasic(BasicHttpAuthenticationFilter.class),
logout(LogoutFilter.class),
noSessionCreation(NoSessionCreationFilter.class),
perms(PermissionsAuthorizationFilter.class),
port(PortFilter.class),
rest(HttpMethodPermissionFilter.class),
roles(RolesAuthorizationFilter.class),
ssl(SslFilter.class),
user(UserFilter.class);
private final Class<? extends Filter> filterClass;
private DefaultFilter(Class<? extends Filter> filterClass) {
this.filterClass = filterClass;
}
public Filter newInstance() {
return (Filter) ClassUtils.newInstance(this.filterClass);
}
public Class<? extends Filter> getFilterClass() {
return this.filterClass;
}
public static Map<String, Filter> createInstanceMap(FilterConfig config) {
Map<String, Filter> filters = new LinkedHashMap<String, Filter>(values().length);
for (DefaultFilter defaultFilter : values()) {
Filter filter = defaultFilter.newInstance();
if (config != null) {
try {
filter.init(config);
} catch (ServletException e) {
String msg = "Unable to correctly init default filter instance of type " +
filter.getClass().getName();
throw new IllegalStateException(msg, e);
}
}
filters.put(defaultFilter.name(), filter);
}
return filters;
}
}