shiro集成spring

2021-01-23

第三章:shiro集成spring

官方文档:https://shiro.apache.org/spring.html

数据库

参考附件《db_shiro.sql》

该数据库用户表(t_user)所有用户密码均为123456,加密的salt盐值为当前用户的用户名(username)

用户表(t_user)

image-20191205110746328

角色表(t_role)

image-20191205110842833

用户角色关系表(t_user_role)

image-20191205110959693

权限表(t_permission)

image-20191205110635487

角色权限关系表(t_role_permission)

image-20191205111112236

ssm环境搭建

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<!-- spring版本 -->
<spring.version>5.0.2.RELEASE</spring.version>
<!-- 日志版本 -->
<slf4j.version>1.6.6</slf4j.version>
<log4j.version>1.2.12</log4j.version>
<!-- mybatis版本 -->
<mybatis.version>3.4.5</mybatis.version>
<!-- shiro版本 -->
<shiro.version>1.4.2</shiro.version>
</properties>

<!-- jar依赖 -->
<dependencies>
<!-- spring start -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</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-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</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-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>

<!-- spring end -->

<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</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.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>

<!-- log start -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- log end -->

<!-- mybatis 依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>

<!-- mybatis整合spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>

<!-- 数据源 -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
<type>jar</type>
<scope>compile</scope>
</dependency>

<!-- mybatis分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2</version>
</dependency>

<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>

<!-- fast json -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>

<!-- shiro核心依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<!-- shiro web依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>
<!-- shiro整合spring依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>

</dependencies>

需要在build标签加入以下代码

1
2
3
4
5
6
7
8
9
10
11
<resources>
<resource>
<!-- 编译目录 -->
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>

applicationContext.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 开启注解扫描,管理service和dao -->
<context:component-scan base-package="com.bdqn.service,com.bdqn.dao"/>


<context:property-placeholder location="classpath:database.properties"/>

<!-- 配置连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 把交给IOC管理 SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- 加载mybatis-config配置文件 -->
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<!-- 加载mybatis映射文件 可以省略不写,前提是接口名称与映射文件名称同名-->
<property name="mapperLocations">
<list>
<!-- 映射文件的路径 -->
<value>classpath:com/bdqn/dao/**/*.xml</value>
</list>
</property>
<!-- 传入PageHelper的插件 -->
<property name="plugins">
<array>
<!-- 传入插件的对象 -->
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<props>
<prop key="helperDialect">mysql</prop>
<prop key="reasonable">true</prop>
</props>
</property>
</bean>
</array>
</property>
</bean>
<!-- 扫描dao接口 -->
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.bdqn.dao"/>
</bean>

<!-- 配置Spring的声明式事务管理 -->
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

<tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

spring mvc.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">

<!-- 扫描controller的注解,别的不扫描 -->
<context:component-scan base-package="com.bdqn.controller"/>

<!-- 配置视图解析器 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- JSP文件所在的目录 -->
<property name="prefix" value="/WEB-INF/jsp/" />
<!-- 文件的后缀名 -->
<property name="suffix" value=".jsp" />
</bean>

<!-- 设置静态资源不过滤 -->
<mvc:resources mapping="/statics/**" location="/statics/"/>

<!-- 开启对SpringMVC注解的支持 -->
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
<value>text/plain;charset=UTF-8</value>
<value>application/xml;charset=UTF-8</value>
</list>
</property>
</bean>
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
<value>text/plain;charset=UTF-8</value>
<value>application/xml;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>



<!--
支持AOP的注解支持,AOP底层使用代理技术
JDK动态代理,要求必须有接口
cglib代理,生成子类对象,proxy-target-class="true" 默认使用cglib的方式
-->
<aop:aspectj-autoproxy proxy-target-class="true"/>

</beans>

mybatis.xml

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置别名 -->
<typeAliases>
<package name="com.bdqn.entity" />
</typeAliases>
</configuration>

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<?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">

<!-- 加载spring配置文件 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 上下文参数配置 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<!-- 使用*号通配符,通配符前面的字符要一致 -->
<param-value>classpath:applicationContext*.xml</param-value>
</context-param>

<!-- 加载springmvc配置文件 -->
<servlet>
<!-- servlet名称,名称自定义,名称唯一 -->
<servlet-name>springmvc</servlet-name>
<!-- 前端控制器,SpringMVC核心控制器 -->
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- web项目启动,立即加载springmvc配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<!-- springmvc配置文件位置 -->
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<!-- servlet名称,名称自定义 -->
<servlet-name>springmvc</servlet-name>
<!-- 访问路径 /支持restful风格-->
<url-pattern>/</url-pattern>
</servlet-mapping>

<!-- 配置过滤器 -->
<filter>
<filter-name>encodingFilter</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>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<welcome-file-list>
<welcome-file>/login.jsp</welcome-file>
</welcome-file-list>

</web-app>

log4j.properties

1
2
3
4
5
log4j.rootCategory=debug, CONSOLE, LOGFILE
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n

database.properties

1
2
3
4
5
6
7
8
#驱动
jdbc.driver=com.mysql.cj.jdbc.Driver
#连接路径
jdbc.url=jdbc:mysql://localhost:3306/db_shiro?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
#数据库用户名
jdbc.username=root
#数据库密码
jdbc.password=root

login.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>用户登录</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/user/login" method="post">
用户名:<input type="text" name="username"/><br/>
密码:<input type="password" name="userpwd"/><br/>
<input type="submit" value="登录"/>
</form>
</body>
</html>

shiro集成spring

自定义Realm

在com.bdqn.realm包下创建UserRealm类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.bdqn.realm;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

/**
* 自定义UserRealm
*/
public class UserRealm extends AuthorizingRealm {

/**
* 授权(为当前登录的用户授予相应的权限)
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}

/**
* 身份验证(为当前登录用户验证身份)
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
return null;
}
}

applicationContext-shiro.xml

创建applicationContext-shiro配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<!-- 声明凭证匹配器 -->
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="md5"></property>
<property name="hashIterations" value="2"></property>
</bean>

<!-- 声明userRealm -->
<bean id="userRealm" class="com.bdqn.realm.UserRealm">
<!-- 注入凭证匹配器 -->
<property name="credentialsMatcher" ref="credentialsMatcher"></property>
</bean>

<!-- 配置SecurityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 注入realm -->
<property name="realm" ref="userRealm"></property>
</bean>

<!-- 配置shiro的过滤器 这里面的id必须和web.xml里面的配置一样 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean" >
<!-- 注入安全管理器 -->
<property name="securityManager" ref="securityManager"></property>
<!-- 注入未登陆的跳转页面 默认的是webapp/login.jsp-->
<property name="loginUrl" value="/login.jsp"></property>
<!-- 注入未授权的访问页面 -->
<property name="unauthorizedUrl" value="/unauthorized.jsp"></property>
<!-- 配置过滤器链 -->
<property name="filterChainDefinitions">
<value>
<!-- 根路径放行 -->
/=anon
<!-- 放行跳转到登陆页面的路径 -->
/login.jsp=anon
<!-- 放行登陆的请求 -->
/user/login*=anon
<!-- 设置登出的路径 -->
/logout=logout
<!-- 设置其它路径全部拦截 -->
/**=authc
</value>
</property>

</bean>

</beans>

web.xml

修改web.xml文件,添加shiro与spring的整合配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!-- 配置shiro的集成开始 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<!--
这里面的shiroFilter必须和applicationContext-shiro.xml里面的
<bean id="shiroFilter"
class="org.apache.shiro.spring.web.ShiroFilterFactoryBean" >
id属性值一样
-->
<param-name>targetBeanName</param-name>
<param-value>shiroFilter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<!-- 配置shiro的集成结束 -->

使用shiro完成用户登录

实体类

User

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.bdqn.entity;

/**
* 用户类
*/
public class User {
private Integer userid;//用户编号
private String username;//用户名
private String userpwd;//密码
private String realname;//真实姓名

public Integer getUserid() {
return userid;
}

public void setUserid(Integer userid) {
this.userid = userid;
}

public String getUsername() {
return username;
}

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

public String getUserpwd() {
return userpwd;
}

public void setUserpwd(String userpwd) {
this.userpwd = userpwd;
}

public String getRealname() {
return realname;
}

public void setRealname(String realname) {
this.realname = realname;
}
}

Role

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.bdqn.entity;

/**
* 角色类
*/
public class Role {

private Integer roleid;//角色编号
private String rolename;//角色名称

public Integer getRoleid() {
return roleid;
}

public void setRoleid(Integer roleid) {
this.roleid = roleid;
}

public String getRolename() {
return rolename;
}

public void setRolename(String rolename) {
this.rolename = rolename;
}
}

Permission

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.bdqn.entity;

/**
* 权限类
*/
public class Permission {
private Integer permissionid;//权限编号
private String permissionname;//权限名称
private String permissioncode;//权限编码

public Integer getPermissionid() {
return permissionid;
}

public void setPermissionid(Integer permissionid) {
this.permissionid = permissionid;
}

public String getPermissionname() {
return permissionname;
}

public void setPermissionname(String permissionname) {
this.permissionname = permissionname;
}

public String getPermissioncode() {
return permissioncode;
}

public void setPermissioncode(String permissioncode) {
this.permissioncode = permissioncode;
}
}

UserMapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.bdqn.dao;

import com.bdqn.entity.User;

public interface UserMapper {

/**
* 根据用户名查询用户
* @param username 用户
* @return
* @throws Exception
*/
User findUserByUserName(String username) throws Exception;

}

UserMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bdqn.dao.UserMapper">

<!-- 根据用户名查询用户 -->
<select id="findUserByUserName" resultType="com.bdqn.entity.User">
select * from t_user where username = #{username}
</select>

</mapper>

UserService

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.bdqn.service;

import com.bdqn.entity.User;

public interface UserService {
/**
* 根据用户名查询用户
* @param username 用户
* @return
* @throws Exception
*/
User findUserByUserName(String username) throws Exception;
}

UserServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.bdqn.service.impl;

import com.bdqn.dao.UserMapper;
import com.bdqn.entity.User;
import com.bdqn.service.UserService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

@Service
@Transactional
public class UserServiceImpl implements UserService {

@Resource
private UserMapper userMapper;

@Override
public User findUserByUserName(String username) throws Exception {
return userMapper.findUserByUserName(username);
}
}

LoginUserVo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.bdqn.vo;

import com.bdqn.entity.User;

import java.util.Set;

/**
* 登录用户类
*/
public class LoginUserVo {
private User user;//用户信息
private Set<String> roles;//角色列表
private Set<String> permissions;//权限列表

public User getUser() {
return user;
}

public void setUser(User user) {
this.user = user;
}

public Set<String> getRoles() {
return roles;
}

public void setRoles(Set<String> roles) {
this.roles = roles;
}

public Set<String> getPermissions() {
return permissions;
}

public void setPermissions(Set<String> permissions) {
this.permissions = permissions;
}

public LoginUserVo() {
}

/**
* 登录用户
* @param user 当前登录用户信息
* @param roles 当前用户拥有的角色列表
* @param permissions 当前用户拥有的角色列表
*/
public LoginUserVo(User user, Set<String> roles, Set<String> permissions) {
this.user = user;
this.roles = roles;
this.permissions = permissions;
}
}

修改UserRealm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package com.bdqn.realm;

import com.bdqn.entity.User;
import com.bdqn.service.UserService;
import com.bdqn.vo.LoginUserVo;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

import javax.annotation.Resource;

/**
* 自定义UserRealm
*/
public class UserRealm extends AuthorizingRealm {

//注入UserService
@Resource
private UserService userService;

@Override
public String getName() {
return this.getClass().getSimpleName();
}

/**
* 授权(为当前登录的用户授予相应的权限)
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}

/**
* 身份验证(为当前登录用户验证身份)
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
try {
//获取当前登录的主体对象
String username = authenticationToken.getPrincipal().toString();
//调用根据用户名查询用户信息的方法
User user= userService.findUserByUserName(username);
//判断对象是否为空
if(user!=null){
//创建登录用户对象,传入用户信息,角色列表,权限列表
LoginUserVo loginUser = new LoginUserVo(user,null,null);
//创建盐值(以用户名作为盐值)
ByteSource salt = ByteSource.Util.bytes(user.getUsername());
//创建身份验证对象
//参数1:当前登录对象 参数2:密码 参数3:盐值 参数4:域名
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(loginUser,user.getUserpwd(),salt,getName());
return info;
}
} catch (Exception e) {
e.printStackTrace();
}
//验证失败
return null;
}
}

image-20191205173005432

UserController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.bdqn.controller;

import com.bdqn.entity.User;
import com.bdqn.vo.LoginUserVo;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpSession;

@Controller
@RequestMapping("/user")
public class UserController {

/**
* 用户登录
* @param user
* @param session
* @return
*/
@PostMapping("/login")
public String login(User user, HttpSession session){
//得到当前登录主体
Subject subject = SecurityUtils.getSubject();
//创建口令
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(),user.getUserpwd());
try {
//登录
subject.login(token);
//创建登录对象
LoginUserVo loginUserVo = (LoginUserVo) subject.getPrincipal();
//保存会话
session.setAttribute("loginUser",loginUserVo.getUser());
//跳转到后台主页
return "main";
} catch (AuthenticationException e) {
e.printStackTrace();
}
//登录失败
return "redirect:/login.jsp";
}

}

main.jsp

在WEB-INF/jsp/下添加main.jsp页面

1
2
3
4
5
6
7
8
9
10
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>后台管理系统首页</title>
</head>
<body>
<h1 style="color: red">shiro课程学习</h1>
<a href="#">退出系统</a>
</body>
</html>

使用shiro完成授权

RoleMapper

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.bdqn.dao;

import java.util.Set;

public interface RoleMapper {
/**
* 根据用户id查询该用户拥有的角色列表
* @param userId 用户编号
* @return
* @throws Exception
*/
Set<String> findRoleListByUserId(int userId) throws Exception;
}

RoleMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bdqn.dao.RoleMapper">

<!-- 根据用户id查询该用户拥有的角色列表 -->
<select id="findRoleListByUserId" resultType="string">
SELECT r.rolename FROM t_role r
INNER JOIN t_user_role ur ON ur.`roleid` = r.`roleid`
WHERE ur.`userid` = #{userId}
</select>

</mapper>

RoleService

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.bdqn.service;

import java.util.Set;

public interface RoleService {
/**
* 根据用户id查询该用户拥有的角色列表
* @param userId 用户编号
* @return
* @throws Exception
*/
Set<String> findRoleListByUserId(int userId) throws Exception;
}

RoleServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.bdqn.service.impl;

import com.bdqn.dao.RoleMapper;
import com.bdqn.service.RoleService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Set;

@Service
@Transactional
public class RoleServiceImpl implements RoleService {

@Resource
private RoleMapper roleMapper;

@Override
public Set<String> findRoleListByUserId(int userId) throws Exception {
//调用根据用户编号查该用户拥有的角色信息
return roleMapper.findRoleListByUserId(userId);
}
}

PermissionMapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.bdqn.dao;

import java.util.Set;

public interface PermissionMapper {

/**
* 根据用户id查询该用户拥有的权限
* @param userId
* @return
* @throws Exception
*/
Set<String> findPermissionListByUserId(int userId) throws Exception;

}

PermissionMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bdqn.dao.PermissionMapper">

<select id="findPermissionListByUserId" resultType="string">
SELECT p.permissioncode FROM t_permission p
INNER JOIN t_role_permission rp ON rp.perid = p.permissionid
INNER JOIN t_user_role ur ON ur.`roleid` = rp.roleid
WHERE ur.`userid` = #{userId}
</select>

</mapper>

PermissionService

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.bdqn.service;

import java.util.Set;

public interface PermissionService {
/**
* 根据用户id查询该用户拥有的权限
* @param userId
* @return
* @throws Exception
*/
Set<String> findPermissionListByUserId(int userId) throws Exception;
}

PermissionServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.bdqn.service.impl;

import com.bdqn.dao.PermissionMapper;
import com.bdqn.service.PermissionService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Set;

@Service
@Transactional
public class PermissionServiceImpl implements PermissionService {

@Resource
private PermissionMapper permissionMapper;

@Override
public Set<String> findPermissionListByUserId(int userId) throws Exception {
//调用根据用户编号查用户拥有的权限
return permissionMapper.findPermissionListByUserId(userId);
}
}

UserController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@ResponseBody
@RequestMapping("/userAdd")
public String userAdd(){
return "userAdd";
}
@ResponseBody
@RequestMapping("/userUpdate")
public String userUpdate(){
return "userUpdate";
}
@ResponseBody
@RequestMapping("/userDelete")
public String userDelete(){
return "userDelete";
}
@ResponseBody
@RequestMapping("/userQuery")
public String userQuery(){
return "userQuery";
}
@ResponseBody
@RequestMapping("/userExport")
public String userExport(){
return "userExport";
}

修改UserRealm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package com.bdqn.realm;

import com.bdqn.entity.Permission;
import com.bdqn.entity.Role;
import com.bdqn.entity.User;
import com.bdqn.service.PermissionService;
import com.bdqn.service.RoleService;
import com.bdqn.service.UserService;
import com.bdqn.vo.LoginUserVo;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

import javax.annotation.Resource;
import java.util.Set;

/**
* 自定义Realm
*/
public class UserRealm extends AuthorizingRealm {

//注入UserService
@Resource
private UserService userService;

@Resource
private RoleService roleService;

@Resource
private PermissionService permissionService;

@Override
public String getName() {
return this.getClass().getSimpleName();
}

/**
* 身份验证(为当前登录的用户进行身份验证)
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
try {
//得到当前登录用户的信息
String username = (String) token.getPrincipal();
//调用根据用户名查询用户信息的方法
User user = userService.findUserByUserName(username);
//判断对象是否为空,不为空表示用户是存在的
if(user!=null){
//查询角色列表
Set<String> roles = roleService.findRoleListByUserId(user.getUserid());
//查询权限列表
Set<String> permissions = permissionService.findPermissionListByUserId(user.getUserid());
//创建登录用户对象
LoginUserVo loginUserVo = new LoginUserVo(user,roles,permissions);
//创建盐值(以用户名作为盐值)
ByteSource salt = ByteSource.Util.bytes(user.getUsername());
//验证用户名及密码是否正确
//参数1:用户名(登录对象) 参数2:密码 参数3:加密的盐值 参数4:域名(填写任意的字符串皆可)
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(loginUserVo,
user.getUserpwd(),salt,this.getName());
//如果info对象能够正常运行,表示登录成功
return info;
}
} catch (Exception e) {
e.printStackTrace();
}
//登录失败
return null;
}

/**
* 授权(为当前登录的用户进行授权)
* @param principals 当前登录对象(主体)
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//获取当前登录对象
LoginUserVo loginUserVo = (LoginUserVo) principals.getPrimaryPrincipal();
//获取当前登录用户拥有的角色列表
Set<String> roles = loginUserVo.getRoles();
//获取当前登录用户拥有哪些权限列表
Set<String> permissions = loginUserVo.getPermissions();
//创建授权对象
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//判断当前角色列表是否为空
if(roles!=null && roles.size()>0){
info.setRoles(roles);//将角色授权于当前用户
}
//判断当前权限列表是否为空
if(permissions!=null && permissions.size()>0){
info.setStringPermissions(permissions);
}
return info;
}


}

image-20191216121424408

修改main.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<html>
<head>
<title>后台管理系统首页</title>
</head>
<body>
<h1 style="color: red">shiro课程学习</h1>
<shiro:hasPermission name="user:query">
<a href="/user/userQuery">查询用户</a><br>
</shiro:hasPermission>
<shiro:hasPermission name="user:add">
<a href="/user/userAdd">添加用户</a><br>
</shiro:hasPermission>
<shiro:hasPermission name="user:update">
<a href="/user/userDelete">删除用户</a><br>
</shiro:hasPermission>
<shiro:hasPermission name="user:delete">
<a href="/user/userUpdate">修改用户</a><br>
</shiro:hasPermission>
<shiro:hasPermission name="user:export">
<a href="/user/userExport">导出用户</a><br>
</shiro:hasPermission>
<a href="/logout">退出系统</a>
</body>
</html>

权限不足

修改applicationContex-shiro.xml文件

image-20191207233829059

注入未授权页面

image-20191207233921721

创建unauthorized.jsp

在根目录下新建此页面

1
2
3
4
5
6
7
8
9
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>权限不足</title>
</head>
<body>
<h2>权限不足</h2>
</body>
</html>

shiro注解式授权

常用注解介绍

  1. @RequiresAuthenthentication:表示当前Subject已经通过login进行身份验证;即 Subject.isAuthenticated()返回 true
  2. @RequiresUser:表示当前Subject已经身份验证或者通过记住我登录的
  3. @RequiresGuest:表示当前Subject没有身份验证或者通过记住我登录过,即是游客身份
  4. @RequiresRoles(value = {“admin”,”user”},logical = Logical.AND):表示当前Subject需要角色admin和user
  5. @RequiresPermissions(value = {“user:delete”,”user:b”},logical = Logical.OR):表示当前Subject需要权限user:delete或者user:b

注解式授权配置

官方文档:https://shiro.apache.org/spring.html

使用注解式授权需要在spring配置文件中添加以下代码配置:

1
2
3
4
5
6
7
<!-- 以下是开启权限注解的配置 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>

注意:上述代码在applicationContext-shiro.xml文件中配置

注解式授权案例

EmployeeController

创建EmployeeController控制器,编写3个功能方法,分别是find(),add(),delete()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.bdqn.controller;

import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/employee")
public class EmployeeController {


//访问该方法需要超级管理员的角色
@RequiresRoles(value = {"超级管理员"})
@RequestMapping("/find")
public String find(){
return "查询员工信息";
}
//访问该方法需要超级管理员的角色或经理的角色
@RequiresRoles(value = {"超级管理员","经理"},logical = Logical.OR)
@RequestMapping("/add")
public String add(){
return "添加员工信息";
}

//访问该方法需要删除用户的权限
@RequiresPermissions(value = {"user:delete"})
@RequestMapping("/delete")
public String delete(){
return "删除员工信息";
}

}

注意:权限注解可以是类级别或方法级别,上述用法是方法级别,用法与spring mvc的@RequestMapping注解类似

权限不足页面跳转配置

使用spring mvc提供的全局异常进行跳转,以下代码在applicationContext-shiro.xml文件中配置

在WEB-INF/jsp/下添加unauthorized.jsp页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 以下是权限不足时跳转页面的配置 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true"></property>
</bean>
<bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="org.apache.shiro.authz.UnauthorizedException">
unauthorized
</prop>
</props>
</property>
</bean>

全局异常配置(注解方式)

定义全局异常类,使用@ControllerAdvice注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.bdqn.exception;

import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandlerAdvice {

/**
* 未授权
* 程序一旦抛出UnauthorizedException就会触发该方法
*/
@ExceptionHandler(value= {UnauthorizedException.class})
public String unauthorized() {
return "unauthorized";
}
}

在applicationContext.xml配置文件中扫描注解

1
<context:component-scan base-package="com.bdqn.service,com.bdqn.dao,com.bdqn.exception"/>

测试

分别登录不同角色的用户进行功能测试