Shiro快速入門 —— 10.項目實例

本系列博文目錄:http://www.javashuo.com/article/p-ewndobct-kn.htmljavascript

 

本文所提供的項目實例,是我將公司項目中的shiro代碼進行了抽取、整理並添加了一些註釋而造成的。html

因此例子中並不包含shiro全部的功能,可是本系列文章前9篇所講解的內容在這裏都是能夠找到的。java

 

本示例項目所使用的技術以下:linux

集成開發環境爲IDEA,項目構建使用spring boot,包管理使用maven,頁面展現使用freemaker,控制層使用spring mvc等。git

在本篇博文中會貼出主要代碼,完整的項目已經上傳到碼雲你們能夠下載查看使用。web

項目碼雲地址:http://git.oschina.net/imlichao/shiro-exampleajax

 

項目結構

freemaker配置文件

package pub.lichao.shiro.config;

import com.jagregory.shiro.freemarker.ShiroTags;
import org.springframework.boot.autoconfigure.freemarker.FreeMarkerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;

import java.util.HashMap;
import java.util.Map;

/**
 * FreeMarker配置文件
 */
@Configuration
public class FreemarkerConfig {

    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer(FreeMarkerProperties freeMarkerProperties) {
        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        configurer.setTemplateLoaderPaths(freeMarkerProperties.getTemplateLoaderPath()); //模板加載路徑默認 "classpath:/templates/"
        configurer.setDefaultEncoding("utf-8");//設置頁面默認編碼(不設置頁面中文亂碼)
        Map<String,Object> variables=new HashMap<String,Object>();
        variables.put("shiro", new ShiroTags());
        configurer.setFreemarkerVariables(variables);//添加shiro自定義標籤
        return configurer;
    }

}

shiro配置文件

package pub.lichao.shiro.config;

import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;
import pub.lichao.shiro.shiro.AuthenticationFilter;
import pub.lichao.shiro.shiro.ShiroRealm;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Shiro配置
 */
@Configuration
public class ShiroConfig {

    /**
     * 建立EhCache緩存類
     * @return
     */
    @Bean(name = "shiroCacheManager")
    public EhCacheManager shiroCacheManager() {
        EhCacheManager ehCacheManager = new EhCacheManager();
        ehCacheManager.setCacheManagerConfigFile("classpath:shiro-ehcache.xml");//指定緩存配置文件路徑
        return ehCacheManager;
    }


    /**
     * 建立安全認證資源類
     * (本身實現的登錄和受權認證規則)
     */
    @Bean(name = "shiroRealm")
    public ShiroRealm shiroRealm(EhCacheManager shiroCacheManager) {
        ShiroRealm realm = new ShiroRealm();
        realm.setCacheManager(shiroCacheManager); //爲資源類配置緩存
        return realm;
    }

    /**
     * 建立保存記住我信息的Cookie
     */
    @Bean(name = "rememberMeCookie")
    public SimpleCookie getSimpleCookie() {
        SimpleCookie simpleCookie = new SimpleCookie();
        simpleCookie.setName("rememberMe");//cookie名字
        simpleCookie.setHttpOnly(true); //設置cookieHttpOnly,保證cookie安全
        simpleCookie.setMaxAge(604800); //保存7天 單位秒
        return simpleCookie;
    }

    /**
     * 建立記住我管理器
     */
    @Bean(name = "rememberMeManager")
    public CookieRememberMeManager getCookieRememberMeManager(SimpleCookie rememberMeCookie) {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        byte[] cipherKey = Base64.decode("wGiHplamyXlVB11UXWol8g==");//建立cookie祕鑰
        cookieRememberMeManager.setCipherKey(cipherKey); //存入cookie祕鑰
        cookieRememberMeManager.setCookie(rememberMeCookie); //存入記住我Cookie
        return cookieRememberMeManager;
    }

    /**
     * 建立默認的安全管理類
     * 整個安全認證流程的管理都由此類負責
     */
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager securityManager(ShiroRealm shiroRealm,EhCacheManager shiroCacheManager,CookieRememberMeManager rememberMeManager) {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); //建立安全管理類
        defaultWebSecurityManager.setRealm(shiroRealm); //指定資源類
        defaultWebSecurityManager.setCacheManager(shiroCacheManager);//爲管理類配置Session緩存
        defaultWebSecurityManager.setRememberMeManager(rememberMeManager);//配置記住我cookie管理類
        return defaultWebSecurityManager;
    }

    /**
     * 得到攔截器工廠類
     */
    @Bean (name = "authenticationFilter")
    public AuthenticationFilter authenticationFilter() {
        return new AuthenticationFilter();
    }
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager,AuthenticationFilter authenticationFilter) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);//設置SecurityManager,必輸
        shiroFilterFactoryBean.setLoginUrl("/login");//配置登陸路徑(登陸頁的路徑和表單提交的路徑必須是同一個,頁面的GET方式,表單的POST方式)
        shiroFilterFactoryBean.setSuccessUrl("/home");//配置登陸成功頁路徑
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");//配置沒有權限跳轉的頁面

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        filterChainDefinitionMap.put("/", "anon"); //無需登陸認證和受權就可訪問的路徑使用anon攔截器
        filterChainDefinitionMap.put("/home/**", "user");//須要登陸認證的路徑使用authc或user攔截器
        filterChainDefinitionMap.put("/user/**", "user,perms[user-jurisdiction]");//須要權限受權的路徑使用perms攔截器
        filterChainDefinitionMap.put("/admin/**", "user,perms[admin-jurisdiction]");//authc和perms攔截器可同時使用
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);//設置攔截規則

        Map<String, Filter> map = new HashMap<String, Filter>();
        map.put("authc", authenticationFilter);//自定義攔截器覆蓋了FormAuthenticationFilter登陸攔截器所用的攔截器名authc
        shiroFilterFactoryBean.setFilters(map);//添加自定義攔截器

        return shiroFilterFactoryBean;
    }

    /**
     * 註冊shiro攔截器
     */
    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
        filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter")); //建立代理攔截器,並指定代理shiro攔截器
        filterRegistration.addInitParameter("targetFilterLifecycle", "true");//設置攔截器生命週期管理規則。false(默認)由SpringApplicationContext管理,true由ServletContainer管理。
        filterRegistration.setEnabled(true);// 激活註冊攔截器
        filterRegistration.addUrlPatterns("/*");//添加攔截路徑
        return filterRegistration;
    }
}

主頁Controller層

package pub.lichao.shiro.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * Controller - 主頁
 */
@Controller
public class HomeController {

    /**
     * 進入主頁
     * @param request
     * @param redirectAttributes
     * @return
     */
    @RequestMapping(value = "/home", method = RequestMethod.GET)
    public String home(HttpServletRequest request, RedirectAttributes redirectAttributes) {
        return "home";
    }

    /**
     * 進入無權限提示頁
     * @param request
     * @param redirectAttributes
     * @return
     */
    @RequestMapping(value = "/unauthorized", method = RequestMethod.GET)
    public String unauthorized(HttpServletRequest request, RedirectAttributes redirectAttributes) {
        return "unauthorized";
    }

    /**
     * 進入admin頁(用戶無此權限)
     */
    @RequestMapping(value = "/admin", method = RequestMethod.GET)
    public String admin(HttpServletRequest request, RedirectAttributes redirectAttributes) {
        return "admin";
    }


}

登陸頁Controller層

package pub.lichao.shiro.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * Controller - 登錄
 */
@Controller
public class LoginController {
    /**
     * 根路徑重定向到登陸頁
     * @return
     */
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String loginForm() {
        return "redirect:login";
    }

    /**
     * 進入登陸頁面
     * @return
     */
    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String loginInput(@ModelAttribute("message") String message) {
        if (message != null && !message.equals("")){
            //此處演示一下重定向到此方法時經過addFlashAttribute添加的參數怎麼獲取
            System.out.println("addFlashAttribute添加的參數 :"+ message);
        }

        //判斷是否已經登陸 或 是否已經記住我
        if (SecurityUtils.getSubject().isAuthenticated() || SecurityUtils.getSubject().isRemembered()) {
            return "redirect:/home";
        } else {
            return "login";
        }
    }

    /**
     * 登陸表單提交
     * @param request
     * @param redirectAttributes
     * @return
     */
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public String login(HttpServletRequest request, RedirectAttributes redirectAttributes) {
        //若是認證未經過得到異常並重定向到登陸頁
        String message = null;
        String loginFailure = (String) request.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);//取得登錄失敗異常

        if (loginFailure.equals("pub.lichao.shiro.shiro.CaptchaAuthenticationException")) {
            message = "驗證碼錯誤";//自定義登錄認證異常 - 用於驗證碼錯誤提示
        } else if (loginFailure.equals("org.apache.shiro.authc.UnknownAccountException")) {
            message = "用戶不存在";//未找到帳戶異常
        }else if (loginFailure.equals("org.apache.shiro.authc.IncorrectCredentialsException")) {
            message = "密碼錯誤";//憑證(密碼)錯誤異常
        } else if (loginFailure.equals("org.apache.shiro.authc.AuthenticationException")) {
            message = "帳號認證失敗";//認證異常
        }else{
            message = "未知認證錯誤";//未知認證錯誤
        }

        //重定向參數傳遞,可以將參數傳遞到最終頁面
        // (用addAttribute的時候參數會寫在url中因此要用addFlashAttribute)
        redirectAttributes.addFlashAttribute("message", message);
        return "redirect:login";

    }

    /**
     * 退出登陸
     * @param redirectAttributes
     * @return
     */
    @RequestMapping(value = "/logout", method = RequestMethod.GET)
    public String logout(RedirectAttributes redirectAttributes) {
        //調用shiro管理工具類的退出登陸方法
        SecurityUtils.getSubject().logout();
        redirectAttributes.addFlashAttribute("message", "您已安全退出");
        return "redirect:login"; //退出後返回到登陸頁
    }
}

 

自定義攔截器

package pub.lichao.shiro.shiro;

import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


/**
 * Filter - 自定義登錄攔截器
 * 繼承並重寫默認的登陸攔截器
 */
public class AuthenticationFilter extends FormAuthenticationFilter {
    /**
     * 建立Token
     */
    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
        String username = getUsername(request);//獲取用戶名 表單name:username
        String password = getPassword(request);//獲取密碼 表單name:password
        boolean rememberMe = isRememberMe(request);//獲取是否記住我 表單name:rememberMe
        String captchaId = WebUtils.getCleanParam(request, "captchaId");//獲取驗證碼id
        String captcha = WebUtils.getCleanParam(request, "captcha");//獲取用戶輸入的驗證碼字符

        return new CaptchaAuthenticationToken(username, password,captchaId, captcha, rememberMe);//存入本身定義的包含驗證碼的Token
    }

    /**
     * 登陸驗證成功以後
     */
    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
        return super.onLoginSuccess(token, subject, request, response);
    }

    /**
     *  當訪問被拒絕
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        //訪問被拒絕時默認行爲是返回登陸頁,可是當使用ajax進行登陸時要返回403錯誤
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String requestType = request.getHeader("X-Requested-With"); //獲取http頭參數X-Requested-With
        if (requestType != null && requestType.equalsIgnoreCase("XMLHttpRequest")) { //頭參數X-Requested-With存在而且值爲XMLHttpRequest說明是ajax請求
            response.sendError(HttpServletResponse.SC_FORBIDDEN); //返回403錯誤 - 執行訪問被禁止
            return false;
        }else{
            return super.onAccessDenied(servletRequest, servletResponse);
        }
    }

}

自定義認證異常

package pub.lichao.shiro.shiro;

import org.apache.shiro.authc.AuthenticationException;

/**
 * 自定義登錄認證異常 - 用於驗證碼錯誤提示
 */
public class CaptchaAuthenticationException extends AuthenticationException {
}

自定義令牌

package pub.lichao.shiro.shiro;

import org.apache.shiro.authc.UsernamePasswordToken;

/**
 * Token - 自定義登陸令牌
 * 繼承並重寫默認的登陸令牌
 */
public class CaptchaAuthenticationToken extends UsernamePasswordToken {

	/**
	 * 自定義構造方法
	 */
	public CaptchaAuthenticationToken(String username, String password, String captchaId, String captcha, boolean rememberMe) {
		super(username, password, rememberMe);
		this.captcha=captcha;
		this.captchaId=captchaId;
	}


	/**
	 * 自定義參數
	 */
	private String captchaId; //驗證碼id
	private String captcha; //錄入的驗證碼字符

	public String getCaptchaId() {
		return captchaId;
	}
	public void setCaptchaId(String captchaId) {
		this.captchaId = captchaId;
	}

	public String getCaptcha() { return captcha; }
	public void setCaptcha(String captcha) {
		this.captcha = captcha;
	}

}

身份信息

package pub.lichao.shiro.shiro;

/**
 * 身份信息
 */
public class Principal implements java.io.Serializable{
    /** 用戶ID */
    private Long userId;
    /** 用戶名 */
    private String username;

    public Principal(Long userId, String username) {
        this.userId = userId;
        this.username = username;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

認證資源

登陸認證和受權邏輯實現都在這裏面算法

/**
 * @(#)ShiroRealm.java
 * Description:
 * Version :	1.0
 * Copyright:	Copyright (c) 苗方清顏 版權全部
 */
package pub.lichao.shiro.shiro;

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import java.util.ArrayList;
import java.util.List;

/**
 * 安全認證資源類
 */
public class ShiroRealm extends AuthorizingRealm {

    /**
     * 登陸認證(身份驗證)
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        CaptchaAuthenticationToken authenticationToken = (CaptchaAuthenticationToken) token; //得到登陸令牌
        String username = authenticationToken.getUsername();
        String password = new String(authenticationToken.getPassword());//將char數組轉換成String類型
        String captchaId = authenticationToken.getCaptchaId();
        String captcha = authenticationToken.getCaptcha();
        // 驗證用戶名密碼和驗證碼是否正確
        usernamePasswordAndCaptchaAuthentication(username,password,captchaId,captcha);
        //建立身份信息類(自定義的)
        Principal principal = new Principal(1L, username);
        //認證經過返回認證信息類
        return new SimpleAuthenticationInfo(principal, password, getName());
    }

    /**
     * 受權
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //獲取當前登陸用戶的信息(登陸認證時取得的)
        Principal principal = (Principal) principals.getPrimaryPrincipal();
        //使用登陸信息中存入的userId獲取當前用戶全部權限
        List<String> authorities = getAuthority(principal.getUserId());
        //建立受權信息類
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        //將權限存入受權信息類
        authorizationInfo.addStringPermissions(authorities);

        return authorizationInfo;
    }

    /**
     * 驗證用戶名密碼和驗證碼是否正確
     * @param username
     * @param password
     * @param captchaId
     * @param captcha
     * @return
     */
    private void usernamePasswordAndCaptchaAuthentication(String username,String password,String captchaId,String captcha){
        //驗證驗證碼是否正確
        if(!captchaId.equals("1") || !captcha.equals("yyyy")){
            throw new CaptchaAuthenticationException(); //驗證碼錯誤時拋出自定義的 驗證碼錯誤異常
        }
        //驗證用戶名是否存在
        if(!username.equals("admin")){
            throw new UnknownAccountException(); //用戶並不存在異常
        }
        //密碼加密(SHA256算法)
        String salt = "c1bac4173f3df3bf0241432a45ac3922";//密言通常由系統爲每一個用戶隨機生成
        String sha256 = new Sha256Hash(password, salt).toString(); //使用sha256進行加密密碼
        //驗證密碼是否正確
        if(!sha256.equals("aa07342954e1ca7170257e74515139cc27710ff703e6fee784d0a4ea1e09f9da")){
            throw new IncorrectCredentialsException();//密碼錯誤異常
        }
    }

    /**
     * 獲取用戶權限
     * @param userId
     * @return
     */
    private List<String> getAuthority(Long userId){
        List<String> authority = new ArrayList<String>();
        if(userId.equals(1L)){
            authority.add("user-jurisdiction");
//          authority.add("admin-jurisdiction");
        }
        return authority;
    }

}

 

緩存配置

<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="shiro-ehcache">
        <!-- 緩存文件存放目錄 -->
        <!-- java.io.tmpdir表明操做系統默認的臨時文件目錄,不一樣操做系統路徑不一樣 -->
        <!-- windows 7   C:\Users\Administrator\AppData\Local\Temp -->
        <!-- linux    /tmp -->
        <diskStore path="${java.io.tmpdir}/shiro/ehcache"/>

        <!-- 設置緩存規則-->
        <!--
          maxElementsInMemory:緩存文件在內存上最大數目
          maxElementsOnDisk:緩存文件在磁盤上的最大數目
          eternal:緩存是否永不過時。true永不過時,false會過時
          timeToIdleSeconds :緩存最大空閒時間,空閒超過此時間則過時(單位:秒)。當eternal爲false時有效
          timeToLiveSeconds :緩存最大的生存時間,從建立開始超過這個時間則過時(單位:秒)。當eternal爲false時有效
          overflowToDisk:若是內存中數據超過內存限制,是否緩存到磁盤上
          diskPersistent:是否在磁盤上持久化緩存,系統重啓後緩存依然存在
        -->
        <defaultCache
                maxElementsInMemory="1000"
                maxElementsOnDisk="10000"
                eternal="false"
                timeToIdleSeconds="300"
                timeToLiveSeconds="600"
                overflowToDisk="true"
                diskPersistent="false"/>
</ehcache>

 

view層代碼

loginspring

<!DOCTYPE html>
<html lang="en" class="no-js">
    <head>
        <title>管理平臺</title>
    </head>

    <body style="text-align:center">
        <div style="margin:0 auto;">
            <form action="/login" method="post">
                <h3>管理平臺登陸</h3>
                <label>用戶名:</label>
                <input type="text" placeholder="請輸入用戶名" name="username" id="username" value="admin"/><br><br>
                <label>密&nbsp;碼:</label>
                <input type="password" placeholder="請輸入密碼" name="password" id="password" value="111111"/><br><br>
                <label>驗證碼:</label>
                <input type="text" placeholder="請輸入驗證碼" name="captcha" id="captcha" value="yyyy"/><br><br>
                <input type="hidden" name="captchaId" id="captchaId" value="1"/>
                <label>保持登陸:</label>
                <input type="checkbox" name="rememberMe" id="rememberMe" />(保持7天)<br><br>
                <button type="submit">登陸</button><br><br>
                <#if message??><span style="color: red" >${message}</span></#if>

            </form>
        </div>
    </body>
</html>

home apache

<!DOCTYPE html>
<html lang="en" class="no-js">
    <head>
        <title>管理平臺</title>
    </head>

    <body style="text-align:center">
        <h1>歡迎登陸管理平臺!</h1>
        <br><br>
        <a href="logout">退出登陸</a>
        <br><br>
        <@shiro.hasPermission name = "user-jurisdiction">
            用戶擁有user-jurisdiction權限才能看見此內容!
        </@shiro.hasPermission>
        <br><br>
        <button onclick="javascript:window.location.href='admin'">
            進入到admin頁(須要有權限)
        </button>
    </body>
</html>

admin

<!DOCTYPE html>
<html lang="en" class="no-js">
    <head>
        <title>管理平臺</title>
    </head>

    <body style="text-align:center">
        <h1>用戶須要有admin-jurisdiction權限才能看到此頁內容</h1><br><br>
        <a href="javascript:history.go(-1)">返回</a>
    </body>
</html>

unauthorized

<!DOCTYPE html>
<html lang="en" class="no-js">
    <head>
        <title>管理平臺</title>
    </head>

    <body style="text-align:center">
        你沒有訪問此功能的權限!
        <a href="javascript:history.go(-1)">返回</a>
    </body>
</html>
相關文章
相關標籤/搜索