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>
相关文章
相关标签/搜索