Shiro快速入门 —— 10.项目实例

本系列博文目录:https://my.oschina.net/u/3452433/blog/907396

 

本文所提供的项目实例,是我将公司项目中的shiro代码进行了抽取、整理并添加了一些注释而形成的。

所以例子中并不包含shiro所有的功能,但是本系列文章前9篇所讲解的内容在这里都是可以找到的。

 

本示例项目所使用的技术如下:

集成开发环境为IDEA,项目构建使用spring boot,包管理使用maven,页面展示使用freemaker,控制层使用spring mvc等。

在本篇博文中会贴出主要代码,完整的项目已经上传到码云大家可以下载查看使用。

项目码云地址:http://git.oschina.net/imlichao/shiro-example

 

项目结构

172534_rCc4_3452433.png

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层代码

login

<!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 

<!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>

 

转载于:https://my.oschina.net/u/3452433/blog/907395

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值