【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;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章