Shiro集成Spring和SpringBoot
1.Spring+JSP集成shiro
1.pom坐标
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>Shiro</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.1.1.RELEASE</spring.version>
<!-- <shiro.version>1.2.2</shiro.version> -->
<shiro.version>1.3.2</shiro.version>
</properties>
<dependencies>
<!-- spring mvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- spring begin -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.10</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.10</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.8</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.8.3</version>
</dependency>
<dependency>
<groupId>commons-chain</groupId>
<artifactId>commons-chain</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-math3</artifactId>
<version>3.5</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-digester3</artifactId>
<version>3.2</version>
</dependency>
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.3</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.2.5</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.5</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-email</artifactId>
<version>1.3.3</version>
</dependency>
<!-- dbcp -->
<!-- <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId>
<version>1.4</version> </dependency> <dependency> <groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId> <version>2.2</version> </dependency> -->
<!-- c3p0 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5-pre8</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>mchange-commons-java</artifactId>
<version>0.2.12</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.20</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis.generator/mybatis-generator-core -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
</dependencies>
<configuration>
<!--配置文件的路径 -->
<configurationFile>${basedir}/src/main/resources/generatorConfig.xml</configurationFile>
<overwrite>true</overwrite>
</configuration>
</plugin>
<!-- <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>-->
<!-- <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.1.1</version>
<configuration>
<webXml>src/main/webapp\WEB-INF\web.xml</webXml>
<warSourceDirectory>src/main/webapp</warSourceDirectory>
</configuration>
</plugin>
-->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<!-- 指定端口 -->
<port>8080</port>
<!-- 请求路径 -->
<path>/shiro</path>
<uriEncoding>UTF-8</uriEncoding>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<!-- 部署时需要携带配置文件 -->
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
<!-- c -->
<include>**/*.tld</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
<include>**/*.tld</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
2.web.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<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_4_0.xsd"
version="4.0">
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- <listener>
<listener-class>com.oracle.listener.CommonListener</listener-class>
</listener> -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:spring/spring-root.xml
classpath:spring/spring-shiro.xml
</param-value>
</context-param>
<filter>
<filter-name>characterEncodingFilter</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>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<!--将shiro过滤交给spring管理-->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
3.Spring集成shiro核心配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!--注入安全管理器-->
<property name="securityManager" ref="securityManager" />
<!--登陆的url-->
<property name="loginUrl" value="/admin/login.jsp" />
<!--登录成功后的url-->
<property name="successUrl" value="/admin/common/main" />
<!--未授权的url-->
<property name="unauthorizedUrl" value="/admin/common/unauthorized" />
<!--配置授权地址列表-->
<property name="filterChainDefinitions">
<value>
/ = authc
/admin/login.jsp = authc
/admin/logout.jsp = logout
/admin/product/list = perms["admin:product:list"]
/admin/product/add = perms["admin:product:add"]
/admin/product/update = perms["admin:product:update"]
/admin/product/delete = perms["admin:product:delete"]
/admin/product/view = perms["admin:product:view"]
/admin/category/list = perms["admin:category:list"]
/admin/category/add = perms["admin:category:add"]
/admin/category/update = perms["admin:category:update"]
/admin/category/delete = perms["admin:category:delete"]
/admin/category/view = perms["admin:category:view"]
/admin/** = authc
</value>
</property>
<property name="filters">
<map>
<entry key="authc" value-ref="authenticationFilter" />
<entry key="logout" value-ref="logoutFilter" />
</map>
</property>
</bean>
<!--配置ehcache缓存管理器-->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:spring/shiro-cache.xml"/>
</bean>
<!--登出跳转的url-->
<bean id="logoutFilter" class="org.apache.shiro.web.filter.authc.LogoutFilter">
<property name="redirectUrl" value="/admin/login.jsp" />
</bean>
<!--安全管理器-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="authenticationRealm" />
<property name="cacheManager" ref="cacheManager"></property>
</bean>
<!--自定义认证-->
<bean id="authenticationRealm" class="com.li.shiro.AuthenticationRealm">
</bean>
<bean id="authenticationFilter" class="com.li.filter.AuthenticationFilter" />
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager" />
<property name="arguments" ref="securityManager" />
</bean>
</beans>
4.shiro集成ehcache缓存(本地缓存)
shiro默认会在每次进行权限url授权校验时查询数据库的权限列表,所以使用缓存可减少重复查询次数,默认使用defaultCache默认缓存策略,也可以通过Spring提供的cache注解在业务层方法上指定缓存策略,如果指定则使用的是Spring提供的而不是shiro默认集成的。
shiro-ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" dynamicConfig="false">
<diskStore path="java.io.tmpdir"/>
<!--授权信息缓存-->
<cache name="authorizationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
overflowToDisk="false"
statistics="true">
</cache>
<!--身份信息缓存-->
<cache name="authenticationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
overflowToDisk="false"
statistics="true">
</cache>
<!--session缓存-->
<cache name="activeSessionCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
overflowToDisk="false"
statistics="true">
</cache>
<!--
name:缓存名称。
maxElementsInMemory:缓存最大个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
maxElementsOnDisk:硬盘最大缓存个数。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
-->
<defaultCache name="defaultCache"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="600"
timeToLiveSeconds="600"
overflowToDisk="false"
maxElementsOnDisk="100000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
5.创建AuthenticationToKen自定义令牌类
package com.li.shiro;
import org.apache.shiro.authc.UsernamePasswordToken;
/**
* 自定义令牌
* @author zhaoran
2017年12月31日
*
*/
public class AuthenticationToKen extends UsernamePasswordToken{
//验证码状态
private boolean isValid;
public AuthenticationToKen(String loginName,String password,boolean isValid) {
super(loginName, password);
this.isValid=isValid;
}
public boolean isValid() {
return isValid;
}
public void setValid(boolean isValid) {
this.isValid = isValid;
}
}
6.封装Principal身份牌类
package com.li.shiro;
import java.io.Serializable;
//身份牌
public class Principal implements Serializable{
private Integer id;
private String loginName;
public Principal() {}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
public Principal(Integer id, String loginName) {
super();
this.id = id;
this.loginName = loginName;
}
}
7.AuthenticationFilter 认证过滤器
AuthenticationFilter.java
public class AuthenticationFilter extends FormAuthenticationFilter {
@Override
protected boolean onAccessDenied(ServletRequest req, ServletResponse res) throws Exception {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String requestType = request.getHeader("X-Requested-With");
if (requestType != null && requestType.equalsIgnoreCase("XMLHttpRequest")) {
response.addHeader("loginStatus", "accessDenied");
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return false;
}
return super.onAccessDenied(request, response);
}
//获取登录信息,创建认证令牌
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
//默认为true,表示有验证码不为空
boolean bln=true;
//获取用户名和密码(前台name值必须为username)
String loginName=getUsername(request);
String password=getPassword(request);
HttpServletRequest req=(HttpServletRequest) request;
HttpSession session=req.getSession();
//获取验证码
String captCode=request.getParameter("code");
String sessionCaptCode=(String) session.getAttribute("valcode");
//判断验证码是否为空
if((null==sessionCaptCode)||(null==captCode)) {
bln=false;
}
//判断验证码是否相等
if(sessionCaptCode!=null&&captCode!=null) {
if(!sessionCaptCode.equals(captCode)) {
bln=false;
}
}
//返回自定义令牌
return new AuthenticationToKen(loginName, password,bln);
}
@Override
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
WebUtils.issueRedirect(request, response, getSuccessUrl());
return false;
}
}
8.AuthenticationRealm 身份认证授权
public class AuthenticationRealm extends AuthorizingRealm {
@Resource
private AdminService adminService;
//利用身份牌进行授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//获取自定义身份牌
Principal principal=(Principal) principals.fromRealm(getName()).iterator().next();
if(principal!=null) {
//获取授权的url集合
List<String> listPer=this.adminService.getAdminUrl(principal.getId());
if(listPer!=null) {
//返回认证信息
SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
//添加授权url集合
authorizationInfo.addStringPermissions(listPer);
return authorizationInfo;
}
}
return null;
}
//使用认证令牌信息进行登录认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationToKen authenticationToKen=(AuthenticationToKen) token;
String loginName=authenticationToKen.getUsername();
String password=new String(authenticationToKen.getPassword());
//判断验证码,如果不存在抛异常
if(!authenticationToKen.isValid()) {
throw new UnsupportedTokenException();
}
//判断登录名是否为空
if(loginName!=null&&!"".equals(loginName)) {
//通过登录名获取用户对象
Admins admin=this.adminService.getAdminByLoginName(loginName);
//如果用户为空,抛异常
if(admin==null) {
throw new UnknownAccountException();
}
//判断密码是否正确
if(!admin.getPwd().equals(password)) {
throw new IncorrectCredentialsException();
}
//设置登录日期
admin.setLogindate(new Date());
//更新信息
this.adminService.updateAdmin(admin);
//返回认证信息,此处密码错误会抛异常,
return new SimpleAuthenticationInfo(new Principal(admin.getId(), loginName), password, getName());
}
return null;
}
}
9.登录页login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro" %>
<%@page import="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<shiro:authenticated>
<%
response.sendRedirect(request.getContextPath()+"/admin/common/main");
%>
</shiro:authenticated>
<%--判断抛出的异常对用户进行提示--%>
<%
String loginError = (String) request.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);
if(loginError!=null){
if(loginError.equals("org.apache.shiro.authc.pam.UnsupportedTokenException")){
pageContext.setAttribute("loginError", "验证码错误");
}else if(loginError.equals("org.apache.shiro.authc.UnknownAccountException")){
pageContext.setAttribute("loginError", "用户名不存在或密码不正确");
}else if(loginError.equals("org.apache.shiro.authc.IncorrectCredentialsException")){
pageContext.setAttribute("loginError", "用户名不存在或密码不正确");
}
}
%>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="${root}/resources/css/pintuer.css">
<link rel="stylesheet" href="${root}/resources/css/admin.css">
<script src="${root}/resources/js/jquery.js"></script>
<script src="${root}/resources/js/pintuer.js"></script>
<title>管理员登录</title>
</head>
<body>
<div class="bg"></div>
<div class="container">
<div class="line bouncein">
<div class="xs6 xm4 xs3-move xm4-move">
<div style="height:150px;"></div>
<div class="media media-y margin-big-bottom">
</div>
<form action="login.jsp" method="post">
<div class="panel loginbox">
<div class="text-center margin-big padding-big-top"><h1>后台管理中心</h1></div>
<div class="panel-body" style="padding:30px; padding-bottom:10px; padding-top:10px;">
<div class="form-group">
<div class="field field-icon-right">
<input type="text" class="input input-big" name="username" placeholder="登录账号" data-validate="required:请填写账号" />
<span class="icon icon-user margin-small"></span>
</div>
</div>
<div class="form-group">
<div class="field field-icon-right">
<input type="password" class="input input-big" name="password" placeholder="登录密码" data-validate="required:请填写密码" />
<span class="icon icon-key margin-small"></span>
</div>
</div>
<div class="form-group">
<div class="field">
<input type="text" class="input input-big" name="code" placeholder="填写右侧的验证码" data-validate="required:请填写右侧的验证码" />
<img src="${root}/captServlet" alt="" width="100" height="32" class="passcode" style="height:43px;cursor:pointer;" οnclick="this.src=this.src+'?'">
</div>
</div>
</div>
<span style="color: red;font-size: 12px;">${loginError}</span>
<div style="padding:30px;"><input type="submit" class="button button-block bg-main text-big input-big" value="登录"></div>
</div>
</form>
</div>
</div>
</div>
</body>
</html>
10.主页显示判断
<%--判断授权地址--%>
<shiro:hasPermission name="admin:product:view">
<h2><span class="icon-pencil-square-o"></span>产品管理</h2>
<ul>
<li><a href="${root}/admin/product/list" target="right"><span class="icon-caret-right"></span>浏览产品</a></li>
<li><a href="${root}/admin/product/update" target="right"><span class="icon-caret-right"></span>修改产品</a></li>
</ul>
</shiro:hasPermission>
2.SpringBoot+thymeleaf集成Shiro
自定义认证过滤验证码
需要在核心配置类手动配置过滤的类 filters.put(“authc”, new AuthenticationFilter());
用户登录时,会走自定义过滤器由shiro进行登录操作,成功后通过配置类跳转页面,失败后则会抛出异常,由控制层登录方法获取shiro异常进行页面输出。
注意:用户认证成功后,再进登录页无法重新登录,而是会直接走控制层登录方法,session未清除前,只能认证一次,所以登录成功后不能进行登录操作,应先退出登录清除session。
需在核心配置类添加方言,否则页面标签不生效
3-6步同上方整合spring
1.pom坐标
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.li</groupId>
<artifactId>springboot-shiro</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-shiro</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- thymeleaf模板引擎:jsp就是一个模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!-- 热部署:修改完java代码后,tomcat会自动重启 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<!-- MySQL的JDBC驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!--shiro start-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
<!--
第三方数据源:C3P0,DBCP,Druid
-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- 构建,编译 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.4.5</version>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
</build>
</project>
2.SpringBoot中的Shiro核心配置类
/**
* shiro的核心配置类
* 1.配置自定义的Realm类,需要创建realm对象
* 2.配置安全管理器
* 3.配置shiroFilterFactoryBean对象
*/
@Configuration
public class ShiroConfig {
//配置自定义Realm注册为bean
@Bean
public Realm authenticationRealm(){
return new AuthenticationRealm();
}
//配置安全管理器
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(Realm realm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置自定义的Realm
securityManager.setRealm(realm);
return securityManager;
}
//配置shiroFilterFactoryBean对象
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();//获取filters
//将自定义 的FormAuthenticationFilter注入shiroFilter中
filters.put("authc", new AuthenticationFilter());
//拦截器----Map集合
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
/*
* 使用Shiro内置过滤器实现页面拦截:拦截url链接请求
*
* shiro内置过滤器,可以实现权限相关的拦截器
* 常用的过滤器:
* anon:无需认证(登录)可以访问
* authc:必须认证才可以访问
* user:如果使用rememberMe的功能可以直接访问 (记住用户和密码)
* perms:该资源必须得到资源权限才可以访问 (密码验证)
* role:该资源必须得到角色权限才可以访问 (VIP会员)
*
*/
//表示这个为公共资源 一定是在受限资源上面
filterChainDefinitionMap.put("/capt", "anon");
filterChainDefinitionMap.put("/static/**", "anon");
//根据用户角色赋予相应的权限
/* filterChainDefinitionMap.put("/add-success.html", "roles[Commom]");
filterChainDefinitionMap.put("/selete-success.html", "roles[Commom]");
filterChainDefinitionMap.put("/update-success.html", "roles[Member]");
filterChainDefinitionMap.put("/delete-success.html", "roles[Vip]");*/
//根据用户拥有的具体权限赋予相应的权限
filterChainDefinitionMap.put("/admin/product/list", "perms[admin:product:list]");
filterChainDefinitionMap.put("/admin/product/add", "perms[admin:product:add]");
filterChainDefinitionMap.put("/admin/product/update", "perms[admin:product:update]");
filterChainDefinitionMap.put("/admin/product/delete", "perms[admin:product:delete]");
filterChainDefinitionMap.put("/admin/product/view", "perms[admin:product:view]");
filterChainDefinitionMap.put("/admin/category/list", "perms[admin:category:list]");
filterChainDefinitionMap.put("/admin/category/add", "perms[admin:category:add]");
filterChainDefinitionMap.put("/admin/category/update", "perms[admin:category:update]");
filterChainDefinitionMap.put("/admin/category/delete", "perms[admin:category:delete]");
filterChainDefinitionMap.put("/admin/category/view", "perms[admin:category:view]");
// /** 匹配所有的路径
// 通过Map集合组成了一个拦截器链 ,自顶向下过滤,一旦匹配,则不再执行下面的过滤
// 如果下面的定义与上面冲突,那按照了谁先定义谁说了算
// /** 一定要配置在最后
filterChainDefinitionMap.put("/**", "authc");
// 将拦截器链设置到shiro中
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面,没有认证时拦截跳转
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/admin/common/main");
//未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/admin/common/unauthorized");
return shiroFilterFactoryBean;
}
/**
* 开启shiro aop注解支持
* 使用代理方式;所以需要开启代码支持
* @param securityManager
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* 开启cglib代理
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
//加方言,否则标签不生效
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
}
3.创建AuthenticationToKen自定义令牌类
package com.li.shiro;
import org.apache.shiro.authc.UsernamePasswordToken;
/**
* 自定义令牌
* @author zhaoran
2017年12月31日
*
*/
public class AuthenticationToKen extends UsernamePasswordToken{
//验证码状态
private boolean isValid;
public AuthenticationToKen(String loginName,String password,boolean isValid) {
super(loginName, password);
this.isValid=isValid;
}
public boolean isValid() {
return isValid;
}
public void setValid(boolean isValid) {
this.isValid = isValid;
}
}
4.封装Principal身份牌类
package com.li.shiro;
import java.io.Serializable;
//身份牌
public class Principal implements Serializable{
private Integer id;
private String loginName;
public Principal() {}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
public Principal(Integer id, String loginName) {
super();
this.id = id;
this.loginName = loginName;
}
}
5.AuthenticationFilter 认证过滤器
AuthenticationFilter.java
public class AuthenticationFilter extends FormAuthenticationFilter {
@Override
protected boolean onAccessDenied(ServletRequest req, ServletResponse res) throws Exception {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String requestType = request.getHeader("X-Requested-With");
if (requestType != null && requestType.equalsIgnoreCase("XMLHttpRequest")) {
response.addHeader("loginStatus", "accessDenied");
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return false;
}
return super.onAccessDenied(request, response);
}
//获取登录信息,创建认证令牌
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
//默认为true,表示有验证码不为空
boolean bln=true;
//获取用户名和密码(前台name值必须为username)
String loginName=getUsername(request);
String password=getPassword(request);
HttpServletRequest req=(HttpServletRequest) request;
HttpSession session=req.getSession();
//获取验证码
String captCode=request.getParameter("code");
String sessionCaptCode=(String) session.getAttribute("valcode");
//判断验证码是否为空
if((null==sessionCaptCode)||(null==captCode)) {
bln=false;
}
//判断验证码是否相等
if(sessionCaptCode!=null&&captCode!=null) {
if(!sessionCaptCode.equals(captCode)) {
bln=false;
}
}
//返回自定义令牌
return new AuthenticationToKen(loginName, password,bln);
}
@Override
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
WebUtils.issueRedirect(request, response, getSuccessUrl());
return false;
}
}
6.AuthenticationRealm 身份认证授权
public class AuthenticationRealm extends AuthorizingRealm {
@Resource
private AdminService adminService;
//利用身份牌进行授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//获取自定义身份牌
Principal principal=(Principal) principals.fromRealm(getName()).iterator().next();
if(principal!=null) {
//获取授权的url集合
List<String> listPer=this.adminService.getAdminUrl(principal.getId());
if(listPer!=null) {
//返回认证信息
SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
//添加授权url集合
authorizationInfo.addStringPermissions(listPer);
return authorizationInfo;
}
}
return null;
}
//使用认证令牌信息进行登录认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationToKen authenticationToKen=(AuthenticationToKen) token;
String loginName=authenticationToKen.getUsername();
String password=new String(authenticationToKen.getPassword());
//判断验证码,如果不存在抛异常
if(!authenticationToKen.isValid()) {
throw new UnsupportedTokenException();
}
//判断登录名是否为空
if(loginName!=null&&!"".equals(loginName)) {
//通过登录名获取用户对象
Admins admin=this.adminService.getAdminByLoginName(loginName);
//如果用户为空,抛异常
if(admin==null) {
throw new UnknownAccountException();
}
//判断密码是否正确
if(!admin.getPwd().equals(password)) {
throw new IncorrectCredentialsException();
}
//设置登录日期
admin.setLogindate(new Date());
//更新信息
this.adminService.updateAdmin(admin);
//返回认证信息,此处密码错误会抛异常,
return new SimpleAuthenticationInfo(new Principal(admin.getId(), loginName), password, getName());
}
return null;
}
}
7.LoginController登录控制层
/**
* 功能描述:LoginController
* @author https://blog.csdn.net/chen_2890
* @date 2018/12/5 19:41:01
*/
@Controller
public class LoginController {
/**
* 描述:session对象
* @date 2018/12/9 20:35
*/
@Autowired
private HttpSession session;
/**
* 功能描述:login,即shiro的认证
* @return org.springframework.http.ResponseEntity<java.lang.Void>
* @author https://blog.csdn.net/chen_2890
* @date 2018/12/9 20:35
**/
@RequestMapping("/login")
public String login(HttpServletRequest request, Model model){
System.out.println("HomeController.login");
// 登录失败从request中获取shiro处理的异常信息
String loginError = (String) request.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);
if(loginError!=null){
if(loginError.equals("org.apache.shiro.authc.pam.UnsupportedTokenException")){
model.addAttribute("loginError", "验证码错误");
}else if(loginError.equals("org.apache.shiro.authc.UnknownAccountException")){
model.addAttribute("loginError", "用户名不存在或密码不正确");
}else if(loginError.equals("org.apache.shiro.authc.IncorrectCredentialsException")){
model.addAttribute("loginError", "用户名不存在或密码不正确");
}
}
return "admin/comm/login";
}
@RequestMapping("/toLogin")
public String toLogin(){
return "admin/comm/login";
}
/**
* 功能描述: shiro的logout退出,只是将放置PrincipalCollection这个集合置空,
* 删除了session,但是没有清空缓存,需要手动清除缓存
**/
@PutMapping("/logout")
public ResponseEntity<Void> logout(){
//清空session
session.removeAttribute("loginUser");
//退出shiro
SecurityUtils.getSubject().logout();
//返回状态
return new ResponseEntity<>(HttpStatus.OK);
}
/**
* 验证码
* @param response
* @param session
* @throws IOException
*/
@RequestMapping("/capt")
public void captChange(HttpServletResponse response, HttpSession session) throws IOException {
response.setContentType("image/jpeg");
response.setHeader("pragma", "no-cache");
response.setHeader("cache-control", "no-cache");
response.setHeader("expires", "0");
int length = 4;
String valcode = "";
Random rd = new Random();
for (int i = 0; i < length; i++)
valcode += rd.nextInt(10);
session.setAttribute("valcode", valcode);
int width = 80;
int height = 25;
BufferedImage img = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
Graphics g = img.getGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
for (int i = 0; i < 50; i++) {
g.setColor(new Color(rd.nextInt(100) + 155, rd.nextInt(100) + 155,
rd.nextInt(100) + 155));
g.drawLine(rd.nextInt(width), rd.nextInt(height),
rd.nextInt(width), rd.nextInt(height));
}
g.setColor(Color.GRAY);
g.drawRect(0, 0, width - 1, height - 1);
Font[] fonts = { new Font("隶书", Font.BOLD, 18),
new Font("楷体", Font.BOLD, 18), new Font("宋体", Font.BOLD, 18),
new Font("幼圆", Font.BOLD, 18) };
for (int i = 0; i < length; i++) {
g.setColor(new Color(rd.nextInt(150), rd.nextInt(150), rd
.nextInt(150)));
g.setFont(fonts[rd.nextInt(fonts.length)]);
g.drawString(valcode.charAt(i) + "", width / valcode.length() * i
+ 2, 18);
}
g.dispose();
ImageIO.write(img, "jpeg", response.getOutputStream());
}
}
8.登录页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" >
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<form action="/login" method="post">
<div class="panel loginbox">
<div class="text-center margin-big padding-big-top"><h1>后台管理中心</h1></div>
<div class="panel-body" style="padding:30px; padding-bottom:10px; padding-top:10px;">
<div class="form-group">
<div class="field field-icon-right">
<input type="text" class="input input-big" name="username" placeholder="登录账号" data-validate="required:请填写账号" />
<span class="icon icon-user margin-small"></span>
</div>
</div>
<div class="form-group">
<div class="field field-icon-right">
<input type="password" class="input input-big" name="password" placeholder="登录密码" data-validate="required:请填写密码" />
<span class="icon icon-key margin-small"></span>
</div>
</div>
<div class="form-group">
<div class="field">
<input type="text" class="input input-big" name="code" placeholder="填写右侧的验证码" data-validate="required:请填写右侧的验证码" />
<img src="/capt" alt="" width="100" height="32" class="passcode" style="height:43px;cursor:pointer;" onclick="this.src=this.src+'?'">
</div>
</div>
</div>
<span style="color: red;font-size: 12px;" th:text="${loginError}"></span>
<div style="padding:30px;"><input type="submit" class="button button-block bg-main text-big input-big" value="登录"></div>
</div>
</form>
</body>
</html>
9.主页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
<meta charset="UTF-8">
<title>主页面</title>
</head>
<body>
<div shiro:hasPermission="admin:product:view">
<h2><span class="icon-pencil-square-o"></span>产品管理</h2>
<ul>
<li><a href="/admin/product/list" target="right"><span class="icon-caret-right"></span>浏览产品</a></li>
<li><a href="/admin/product/update" target="right"><span class="icon-caret-right"></span>修改产品</a></li>
</ul>
</div>
<div shiro:hasPermission="admin:category:view">
<h2><span class="icon-pencil-square-o"></span>分类管理</h2>
<ul>
<li><a href="/admin/category/list" target="right"><span class="icon-caret-right"></span>浏览分类</a></li>
<li><a href="/admin/category/add" target="right"><span class="icon-caret-right"></span>添加分类</a></li>
</ul>
</div>
<div shiro:hasPermission="admin:role">
<h2><span class="icon-pencil-square-o"></span>系统管理</h2>
<ul>
<li><a href="/admin/role/list" target="right"><span class="icon-caret-right"></span>浏览角色</a></li>
<li><a href="/admin/admin/list" target="right"><span class="icon-caret-right"></span>管理员</a></li>
</ul>
</div>
</body>
</html>