1、Shiro是什么?
Apache Shiro 是 Java 的一个安全框架。
2.Shiro一些基本概念


3.Shiro结合数据库。
RBAC的概念
Roles base access controll
基于角色的权限控制
或者
Resources base access controll
基于资源的权限控制
通俗来说,你得获得对应的资源或者权限才能够进行访问
表结构
最简单的权限控制,是建立在 “用户” —— ”角色“ —— “权限” 之间的关系。
其中用户和角色之间是多对多关系,角色和权限是多对多关系。
在进行表的设计时,可以考虑在用户、角色、权限三张表的基础上,再建立
用户-角色表,角色-权限表来维护他们之间的关系。
所以,最简单的RBAC需要五张表来实现。
4.编码,通过数据库的设计实现最简单的权限控制
SQL:

1 DROP DATABASE IF EXISTS shiro; 2 CREATE DATABASE shiro DEFAULT CHARACTER SET utf8; 3 USE shiro; 4 5 drop table if exists user; 6 drop table if exists role; 7 drop table if exists permission; 8 drop table if exists user_role; 9 drop table if exists role_permission; 10 11 create table user ( 12 id bigint auto_increment, 13 name varchar(100), 14 password varchar(100), 15 constraint pk_users primary key(id) 16 ) charset=utf8 ENGINE=InnoDB; 17 18 create table role ( 19 id bigint auto_increment, 20 name varchar(100), 21 constraint pk_roles primary key(id) 22 ) charset=utf8 ENGINE=InnoDB; 23 24 create table permission ( 25 id bigint auto_increment, 26 name varchar(100), 27 constraint pk_permissions primary key(id) 28 ) charset=utf8 ENGINE=InnoDB; 29 30 create table user_role ( 31 uid bigint, 32 rid bigint, 33 constraint pk_users_roles primary key(uid, rid) 34 ) charset=utf8 ENGINE=InnoDB; 35 36 create table role_permission ( 37 rid bigint, 38 pid bigint, 39 constraint pk_roles_permissions primary key(rid, pid) 40 ) charset=utf8 ENGINE=InnoDB;
插入数据

1 INSERT INTO `permission` VALUES (1,'addProduct'); 2 INSERT INTO `permission` VALUES (2,'deleteProduct'); 3 INSERT INTO `permission` VALUES (3,'editProduct'); 4 INSERT INTO `permission` VALUES (4,'updateProduct'); 5 INSERT INTO `permission` VALUES (5,'listProduct'); 6 INSERT INTO `permission` VALUES (6,'addOrder'); 7 INSERT INTO `permission` VALUES (7,'deleteOrder'); 8 INSERT INTO `permission` VALUES (8,'editOrder'); 9 INSERT INTO `permission` VALUES (9,'updateOrder'); 10 INSERT INTO `permission` VALUES (10,'listOrder'); 11 INSERT INTO `role` VALUES (1,'admin'); 12 INSERT INTO `role` VALUES (2,'productManager'); 13 INSERT INTO `role` VALUES (3,'orderManager'); 14 INSERT INTO `role_permission` VALUES (1,1); 15 INSERT INTO `role_permission` VALUES (1,2); 16 INSERT INTO `role_permission` VALUES (1,3); 17 INSERT INTO `role_permission` VALUES (1,4); 18 INSERT INTO `role_permission` VALUES (1,5); 19 INSERT INTO `role_permission` VALUES (1,6); 20 INSERT INTO `role_permission` VALUES (1,7); 21 INSERT INTO `role_permission` VALUES (1,8); 22 INSERT INTO `role_permission` VALUES (1,9); 23 INSERT INTO `role_permission` VALUES (1,10); 24 INSERT INTO `role_permission` VALUES (2,1); 25 INSERT INTO `role_permission` VALUES (2,2); 26 INSERT INTO `role_permission` VALUES (2,3); 27 INSERT INTO `role_permission` VALUES (2,4); 28 INSERT INTO `role_permission` VALUES (2,5); 29 INSERT INTO `role_permission` VALUES (3,6); 30 INSERT INTO `role_permission` VALUES (3,7); 31 INSERT INTO `role_permission` VALUES (3,8); 32 INSERT INTO `role_permission` VALUES (3,9); 33 INSERT INTO `role_permission` VALUES (3,10); 34 INSERT INTO `user` VALUES (1,'zhang3','12345'); 35 INSERT INTO `user` VALUES (2,'li4','abcde'); 36 INSERT INTO `user_role` VALUES (1,1); 37 INSERT INTO `user_role` VALUES (2,2);
User.java
User类对应user表。

1 public class User {
2 private int id;
3 private String name;
4 private String password;
5
6 public String getName() {
7 return name;
8 }
9
10 public void setName(String name) {
11 this.name = name;
12 }
13
14 public String getPassword() {
15 return password;
16 }
17
18 public void setPassword(String password) {
19 this.password = password;
20 }
21 }
Dao.java
Dao类用于展示权限相关的查询,由于在数据库中限定好了user,roles,permission各自的关系,所以在Dao中不需要提供对User的增删改功能,因此也不需要实现Role,Permission类。

1 public class Dao {
2 private static final String URL = "jdbc:mysql://127.0.0.1:3306/shiro?characterEncoding=UTF-8";
3 private static final String USERNAME = "root";
4 private static final String PASSWORD = "root";
5
6 public Dao() {
7 try {
8 Class.forName("com.mysql.jdbc.Driver");
9 } catch (ClassNotFoundException e) {
10 e.printStackTrace();
11 }
12 }
13
14 public Connection getConnection() throws SQLException {
15 return DriverManager.getConnection(URL, USERNAME, PASSWORD);
16 }
17
18 /**
19 * 根据用户名查询密码
20 * @param username
21 * @return
22 */
23 public String getPassword(String username) {
24 String sql = "SELECT password FROM user WHERE user.name = ? ";
25 PreparedStatement pstmt = null;
26 ResultSet rs = null;
27 Connection c = null;
28
29 try {
30 c = getConnection();
31 pstmt = c.prepareStatement(sql);
32 pstmt.setString(1,username);
33 rs = pstmt.executeQuery();
34 while (rs.next()) {
35 return rs.getString("password");
36 }
37 } catch (SQLException e) {
38 e.printStackTrace();
39 } finally {
40 try {
41 if (rs != null)
42 rs.close();
43 if (pstmt != null)
44 pstmt.close();
45 if (c != null)
46 c.close();
47 } catch (SQLException e) {
48 e.printStackTrace();
49 }
50 }
51 return null;
52 }
53
54 /**
55 * 根据用户名查询角色
56 * @param username
57 * @return
58 */
59 public Set<String> listRoles(String username) {
60 Set<String> roles = new HashSet<>();
61 String sql = "SELECT r.name name FROM user u " +
62 "LEFT JOIN user_role u_r ON u.id = u_r.uid " +
63 "LEFT JOIN role r ON u_r.rid = r.id WHERE u.name = ?";
64 PreparedStatement pstmt = null;
65 ResultSet rs = null;
66 Connection c = null;
67
68 try {
69 c = getConnection();
70 pstmt = c.prepareStatement(sql);
71 pstmt.setString(1,username);
72 rs = pstmt.executeQuery();
73 while (rs.next()) {
74 roles.add(rs.getString("name"));
75 }
76 } catch (SQLException e) {
77 e.printStackTrace();
78 } finally {
79 try {
80 if (rs != null)
81 rs.close();
82 if (pstmt != null)
83 pstmt.close();
84 if (c != null)
85 c.close();
86 } catch (SQLException e) {
87 e.printStackTrace();
88 }
89 }
90 return roles;
91 }
92
93 /**
94 * 根据用户名列出对应的权限
95 * @param username
96 * @return
97 */
98 public Set<String> listPermissions(String username) {
99 //五张表的连接
100 String sql = "SELECT p.name name FROM user u " +
101 "LEFT JOIN user_role u_r ON u.id = u_r.uid " +
102 "LEFT JOIN role r ON u_r.rid = r.id " +
103 "LEFT JOIN role_permission r_p ON r.id = r_p.rid " +
104 "LEFT JOIN permission p ON r_p.pid = p.id WHERE u.name = ?";
105 Set<String> permissions = new HashSet<>();
106 PreparedStatement pstmt = null;
107 ResultSet rs = null;
108 Connection c = null;
109
110 try {
111 c = getConnection();
112 pstmt = c.prepareStatement(sql);
113 pstmt.setString(1,username);
114 rs = pstmt.executeQuery();
115 while (rs.next()) {
116 permissions.add(rs.getString("name"));
117 }
118 } catch (SQLException e) {
119 e.printStackTrace();
120 } finally {
121 try {
122 if (rs != null)
123 rs.close();
124 if (pstmt != null)
125 pstmt.close();
126 if (c != null)
127 c.close();
128 } catch (SQLException e) {
129 e.printStackTrace();
130 }
131 }
132 return permissions;
133 }
134
135 }
main方法

1 Dao dao = new Dao();
2 System.out.println("zhang3");
3 System.out.println(dao.getPassword("zhang3"));
4 System.out.println(dao.listRoles("zhang3"));
5 System.out.println(dao.listPermissions("zhang3"));
6
7 System.out.println("--------------------------------------------------------------vi--------------------------------------------------------------");
8 System.out.println("li4");
9 System.out.println(dao.getPassword("li4"));
10 System.out.println(dao.listRoles("li4"));
11 System.out.println(dao.listPermissions("li4"));
结果如下图:

体验过简单版本的RBAC之后,接下来学习下Shiro是怎么做的。
5.Shiro的认证过程

1.创建一个maven工程,这个就不说了。
2.导入依赖,pom.xml

1 <dependencies> 2 <!--shiro核心--> 3 <dependency> 4 <groupId>org.apache.shiro</groupId> 5 <artifactId>shiro-core</artifactId> 6 <version>1.4.0</version> 7 </dependency> 8 9 <dependency> 10 <groupId>junit</groupId> 11 <artifactId>junit</artifactId> 12 <version>RELEASE</version> 13 </dependency> 14 15 <dependency> 16 <groupId>org.slf4j</groupId> 17 <artifactId>slf4j-nop</artifactId> 18 <version>1.7.28</version> 19 </dependency> 20 </dependencies>
shiro-core是shiro的核心,junit则是为了单元测试,slf4j-nop,尽管网上查了很多博客,包括shiro的入门视频里都没有提到这个包,但是实际使用过程中,没有加上就一直报错。

1 public class testAuthentication {
2 SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();
3
4 @Before
5 public void addUser() {
6 simpleAccountRealm.addAccount("Mark","12345");
7 }
8
9 @Test
10 public void authentication(){
11 //1.创建SecurityManager环境
12 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
13 //2.设置环境
14 SecurityUtils.setSecurityManager(defaultSecurityManager);
15 //3.设置Realm
16 defaultSecurityManager.setRealm(simpleAccountRealm);
17 //4.获取主体
18 Subject subject = SecurityUtils.getSubject();
19 //5.获取用户名密码
20 UsernamePasswordToken token = new UsernamePasswordToken("Mark", "12345");
21 //6.认证
22 try {
23 subject.login(token);
24 } catch (Exception e) {
25 e.printStackTrace();
26 }
27 System.out.println("isAuthentication:"+subject.isAuthenticated());
28 //登出
29 subject.logout();
30 System.out.println("isAuthentication:"+subject.isAuthenticated());
31 }
32 }

6.授权过程

与上面的认证过程基本一致,将之前的代码稍作修改

1 public class testAuthentication {
2 SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();
3
4 @Before
5 public void addUser() {
6 //在password后可以添加角色参数,该参数是一个可变参数
7 simpleAccountRealm.addAccount("Mark","12345","admin","user");
8 }
9
10 @Test
11 public void authentication(){
12 //1.创建SecurityManager环境
13 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
14 //2.设置环境
15 SecurityUtils.setSecurityManager(defaultSecurityManager);
16 //3.设置Realm
17 defaultSecurityManager.setRealm(simpleAccountRealm);
18 //4.获取主体
19 Subject subject = SecurityUtils.getSubject();
20 //5.获取用户名密码
21 UsernamePasswordToken token = new UsernamePasswordToken("Mark", "12345");
22 //6.认证
23 try {
24 subject.login(token);
25
26 } catch (Exception e) {
27 e.printStackTrace();
28 }
29 System.out.println("isAuthentication:"+subject.isAuthenticated());
30 subject.checkRoles("admin1","user");
31 }
32 }
如果checkRole方法中的参数与simpleAccountRealm.addAccount()方法中角色的参数一致,程序正常运行,如果不一致,则会发生UnauthorizedException。
7.IniRealm
IniRealm是Shiro的内置Realm之一,相比之前的SimpleAccountRealm,不仅可以用于认证和角色查询,还增加了权限相关的功能。下面使用IniRealm来进行简单的认证和权限查询。
首先在resources文件夹(类路径下)中创建一个ini文件,用于存储用户、角色、权限相关的数据
account.ini

1 #用户名:Mark 密码:12345 角色:admin 2 [users] 3 Mark=12345,admin 4 #admin角色拥有 对user的add和delete权限 5 [roles] 6 admin=user:delete,user:add
IniRealmDemo.java

1 public class IniRealmDemo {
2 @Test
3 public void testini() {
4 IniRealm iniRealm = new IniRealm("classpath:account.ini");//realm从数据源获取相关数据
5 //1.创建SecurityManager环境
6 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
7 //2.设置环境
8 defaultSecurityManager.setRealm(iniRealm);
9 //3.设置Realm
10 SecurityUtils.setSecurityManager(defaultSecurityManager);
11 //4.获得主体
12 Subject subject = SecurityUtils.getSubject();
13 //5.获取用户名密码
14 UsernamePasswordToken token = new UsernamePasswordToken("Mark", "12345");
15 //6.登录
16 subject.login(token);
17 //7.检测用户是否有admin角色
18 subject.checkRole("admin");
19 //8.检测用户是否有user:add,user:delete权限
20 subject.checkPermissions("user:add","user:delete");
21 }
22 }
8.JdbcRealm
同样是Shiro的内置Realm,可以连接数据库使用。
为了示范它的基本用法,首先导入mysql连接驱动以及druid数据源依赖。

1 <!--mysql驱动--> 2 <dependency> 3 <groupId>mysql</groupId> 4 <artifactId>mysql-connector-java</artifactId> 5 <version>5.1.47</version> 6 </dependency> 7 <!--数据源--> 8 <dependency> 9 <groupId>com.alibaba</groupId> 10 <artifactId>druid</artifactId> 11 <version>1.1.20</version> 12 </dependency>
JdbcRealmDemo.java

1 public class JdbcRealmDemo {
2 DruidDataSource dataSource = new DruidDataSource();
3 {
4 dataSource.setUrl("jdbc:mysql://localhost:3306/shiro");
5 dataSource.setUsername("root");
6 dataSource.setPassword("root");
7 }
8
9 @Test
10 public void testJdbcRealm() {
11 JdbcRealm jdbcRealm = new JdbcRealm();
12 String sql = "SELECT password FROM user WHERE name = ?";
13 //调用自定义的查询语句,进行认证查询
14 jdbcRealm.setAuthenticationQuery(sql);
15 //设置数据源
16 jdbcRealm.setDataSource(dataSource);
17 //1.创建SecurityManager环境
18 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
19 //2.设置环境
20 defaultSecurityManager.setRealm(jdbcRealm);
21 //3.设置Realm
22 SecurityUtils.setSecurityManager(defaultSecurityManager);
23 //4.获取主体
24 Subject subject = SecurityUtils.getSubject();
25 //5.获取用户名密码
26 UsernamePasswordToken token = new UsernamePasswordToken("zhang3", "12345");
27 //6.认证
28 subject.login(token);
29 System.out.println(subject.isAuthenticated());
30 }
31 }
执行结果:

如果修改成没有记录在数据库中的用户名:mayun

发生未知账户异常,就算马云来了也不好使。
如果修改成不对应的密码:admin

发生错误的凭证异常
进行角色验证之前,需要先编写对应的查询角色SQL,然后调用对应的查询方法

1 public void testJdbcRealm() {
2 JdbcRealm jdbcRealm = new JdbcRealm();
3 String sql = "SELECT password FROM user WHERE name = ?";
4 String roleSQL = "SELECT r.name FROM user u" +
5 " LEFT JOIN user_role u_r ON u.id = u_r.uid" +
6 " LEFT JOIN role r ON u_r.rid = r.id WHERE u.name = ?";
7
8 //调用自定义的查询语句,进行认证查询
9 jdbcRealm.setAuthenticationQuery(sql);
10 //角色查询
11 jdbcRealm.setUserRolesQuery(roleSQL);
12 //设置数据源
13 jdbcRealm.setDataSource(dataSource);
14 //1.创建SecurityManager环境
15 DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
16 //2.设置环境
17 defaultSecurityManager.setRealm(jdbcRealm);
18 //3.设置Realm
19 SecurityUtils.setSecurityManager(defaultSecurityManager);
20 //4.获取主体
21 Subject subject = SecurityUtils.getSubject();
22 //5.获取用户名密码
23 UsernamePasswordToken token = new UsernamePasswordToken("zhang3", "12345");
24 //6.认证
25 subject.login(token);
26 System.out.println(subject.isAuthenticated());
27 //查询角色
28 System.out.println("zhang3 -> admin:"+subject.hasRole("admin"));
29 }
执行结果:

9.自定义Realm
JdbcRealm是AuthorizingRealm的子类,我们要实现自定义Realm就需要去继承这个抽象类。
这个类中有两个抽象方法需要我们去实现:
- doGetAuthenticationInfo() 用于认证
- doGetAuthorizationInfo() 用于授权
DatabaseRealm.java

1 public class DatabaseRealm extends AuthorizingRealm {
2
3 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
4 //能进入到这里,表示账号已经通过认证了
5 String username = (String) principals.getPrimaryPrincipal();
6 //通过Dao获取角色和权限
7 Set<String> permissions = new Dao().listPermissions(username);
8 Set<String> roles = new Dao().listRoles(username);
9 //授权对象
10 SimpleAuthorizationInfo sai = new SimpleAuthorizationInfo();
11 //把通过Dao获取到的角色和权限都放进去
12 sai.setStringPermissions(permissions);
13 sai.setRoles(roles);
14 return sai;
15 }
16
17 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
18 //获取账号密码
19 UsernamePasswordToken upt = (UsernamePasswordToken) token;
20 String username = token.getPrincipal().toString();
21 String password = new String(upt.getPassword());
22 //获取数据库中的密码
23 String passwordInDB = new Dao().getPassword(username);
24 //如果为空就是账号不存在,如果不相同就是密码错误,但是都抛出AuthenticationException,而不抛出具体错误原因,以防给破解者提供帮助信息
25 if (null == passwordInDB || !password.equals(passwordInDB)) {
26 throw new AuthenticationException();
27 }
28 //认证信息里存放账号和密码,getName()是当前Realm的继承方法,通常返回当前类名:DatabaseRealm
29 SimpleAuthenticationInfo sai = new SimpleAuthenticationInfo(username,password,getName());
30 return sai;
31 }
32 }
自定义Realm虽然由我们定义,但是却是由Shiro自动调用(使用subject时)。
需要注意的几点:
认证时获取用户名是通过token对象的getPrincipal()方法,获取密码是通过UsernamePasswordToken对象的getPassword方法。存放认证信息,返回的对象是AuthenticationInfo对象,构造方法需要传入
username,password,以及当前类名
授权时,通过PrincipalCollection 对象的getPrimaryPrincipal()方法获取用户名,将获取到的权限和角色数据存入AuthorizationInfo 对象返回。
10.加密
md5加密
在前面的例子中,密码都是明文的,存在巨大风险,通常我们需要将密码加密以后存储到数据库中,采用的加密手段通常都是不可逆的。
什么是不可逆的加密呢?简单来说,输入字符串"123",假设通过计算得到密文"02CB962AC59075B964B07152D2",但是反过来却不能通过计算得到原密码,称这种加密是不可逆的。
md5就是这样的一种加密方法。
把加密后的密文存在数据库中,这样下次登录时,把登录的数据加密以后再进行比较,就可以判断是否正确了,同时避免了暴露密码的风险

1 public void testMd5() {
2 String password = "123";
3 String encryptPwd = new Md5Hash(password).toString();
4 System.out.println(encryptPwd);//结果:202cb962ac59075b964b07152d234b70
5 }
加盐
尽管通过md5是不可逆的加密,但仍然存在一些缺陷,比如说,假如两个人的密码都是"123",那么得到的密文就是相同的,通过这个思路,就可以使用穷举法对一些比较常用的密码进行破解。
考虑到这个原因,我们需要在加密的过程中做“加盐”处理,“盐”在这里可以理解为加密的程度,相同的密码,通过不同程度的加密,也会得到不同的密文。
举个例子,当密码都是123时,在加密之前,给123加上不同的随机值,再进行加密,就会得到不同的密文,这里的随机值就是我们的“盐”,不同的用户对应不同的盐,而盐也需要存进数据库里,与此同时,加密的次数也会导致密文的不同。下面演示如何用Shiro自带的工具类,通过盐来进行md5加密。

1 public void testSalt() {
2 String password = "123";
3 String salt = new SecureRandomNumberGenerator().nextBytes().toString();
4 int times = 2;
5 String algorithmName = "md5";
6 String encryptPwd = new SimpleHash(algorithmName, password, salt, times).toString();
7 System.out.printf("算法为:%s ,明文:%s ,盐:%s ,加密次数:%d ,结果:\n%s",algorithmName,password,salt,times,encryptPwd);
8 }
结果:

为了存储字段"盐",修改表结构,加入"salt"字段。
alter table user add(salt varchar(100));

首先在Dao中增加一个addUser(name,password)方法用于注册。

1 /**
2 * 新增用户
3 * @param name
4 * @param password
5 */
6 public void addUser(String name, String password) {
7 String sql = "INSERT INTO user VALUES(null,?,?,?)";
8 String salt = new SecureRandomNumberGenerator().nextBytes().toString();//盐量随机
9 String encryptPwd = new SimpleHash("md5",password,salt,2).toString(); //md5加密算法,加密2次
10 try {
11 PreparedStatement pstmt = conn.prepareStatement(sql);
12 pstmt.setString(1, name);
13 pstmt.setString(2, encryptPwd);
14 pstmt.setString(3, salt);
15 pstmt.execute();
16 } catch (Exception e) {
17 e.printStackTrace();
18 }
19 }
增加一个getUser()方法,其中不仅包括加密后的密码,还有盐。

1 /**
2 * 获取用户
3 * @return
4 */
5 public User getUser(String username) {
6 String sql = "SELECT * FROM user WHERE name = ?";
7 User user = null;
8 try {
9 PreparedStatement pstmt = conn.prepareStatement(sql);
10 pstmt.setString(1,username);
11 ResultSet rs = pstmt.executeQuery();
12 if(rs.next()) {
13 user = new User();
14 user.setId(rs.getInt("id"));
15 user.setName(rs.getString("name"));
16 user.setPassword(rs.getString("password"));
17 user.setSalt(rs.getString("salt"));
18 }
19 } catch (Exception e) {
20 e.printStackTrace();
21 }
22 return user;
23 }
对DatabaseRealm进行相应的修改,得到传入的用户名密码以后,从数据库中获取对应的密码和盐,对输入密码加盐之后与数据库中的密码比对,最后将用户名密码封装到认证信息中。

1 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
2 //获取账号密码
3 UsernamePasswordToken upt = (UsernamePasswordToken) token;
4 String username = token.getPrincipal().toString();
5 String password = new String(upt.getPassword());
6 String encryptPassword = "";
7 //获取数据库中的密码
8 User user = new Dao().getUser(username);
9 if(user != null) {
10 String passwordInDB = user.getPassword();
11 String salt = user.getSalt();
12 //将传入的密码进行加盐
13 encryptPassword = new SimpleHash("md5",password,user.getSalt(),2).toString();
14 System.out.println("盐:"+user.getSalt());
15 System.out.println("加密后的密码:"+encryptPassword);
16 System.out.println("原密码:"+user.getPassword());
17 }
18 //如果为空就是账号不存在,如果不相同就是密码错误,但是都抛出AuthenticationException,而不抛出具体错误原因,以防给破解者提供帮助信息
19 if (null == user || !encryptPassword.equals(user.getPassword())) {
20 System.out.println("抛了异常!");
21 throw new AuthenticationException();
22 }
23 //认证信息里存放账号和密码,getName()是当前Realm的继承方法,通常返回当前类名:DatabaseRealm
24 SimpleAuthenticationInfo sai = new SimpleAuthenticationInfo(username,password,getName());
25 return sai;
26 }
11.Shiro整合SSM
先贴一个项目结构目录。

- 创建maven工程。这个就不细说了。
- 在pom.xml中导入对应坐标。

1 <?xml version="1.0" encoding="UTF-8"?>
2
3 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5 <modelVersion>4.0.0</modelVersion>
6
7 <groupId>vi</groupId>
8 <artifactId>shiro</artifactId>
9 <version>1.0-SNAPSHOT</version>
10 <packaging>war</packaging>
11
12 <name>shiro Maven Webapp</name>
13 <!-- FIXME change it to the project's website -->
14 <url>http://www.example.com</url>
15
16 <properties>
17 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
18 <maven.compiler.source>1.7</maven.compiler.source>
19 <maven.compiler.target>1.7</maven.compiler.target>
20
21 <!-- lib verion -->
22 <junit.version>4.11</junit.version>
23 <spring.version>5.1.9.RELEASE</spring.version>
24 <mybatis.spring.version>1.2.2</mybatis.spring.version>
25 <mysql.connector.version>5.1.47</mysql.connector.version>
26 <slf4j.version>1.6.6</slf4j.version>
27 <log4j.version>1.2.12</log4j.version>
28 <druid.version>1.0.5</druid.version>
29 <jstl.version>1.2</jstl.version>
30 <commons.fileupload.version>1.3.1</commons.fileupload.version>
31 <shiro.version>1.4.0</shiro.version>
32 </properties>
33
34 <dependencies>
35 <dependency>
36 <groupId>junit</groupId>
37 <artifactId>junit</artifactId>
38 <version>4.12</version>
39 </dependency>
40
41 <!-- mysql-connector -->
42 <dependency>
43 <groupId>mysql</groupId>
44 <artifactId>mysql-connector-java</artifactId>
45 <version>${mysql.connector.version}</version>
46 </dependency>
47
48 <!-- DruidDataSource -->
49 <dependency>
50 <groupId>com.alibaba</groupId>
51 <artifactId>druid</artifactId>
52 <version>${druid.version}</version>
53 </dependency>
54
55 <!-- shiro -->
56 <dependency>
57 <groupId>org.apache.shiro</groupId>
58 <artifactId>shiro-spring</artifactId>
59 <version>${shiro.version}</version>
60 </dependency>
61 <dependency>
62 <groupId>org.apache.shiro</groupId>
63 <artifactId>shiro-ehcache</artifactId>
64 <version>${shiro.version}</version>
65 </dependency>
66 <dependency>
67 <groupId>org.apache.shiro</groupId>
68 <artifactId>shiro-core</artifactId>
69 <version>${shiro.version}</version>
70 </dependency>
71 <dependency>
72 <groupId>org.apache.shiro</groupId>
73 <artifactId>shiro-web</artifactId>
74 <version>${shiro.version}</version>
75 </dependency>
76 <dependency>
77 <groupId>org.apache.shiro</groupId>
78 <artifactId>shiro-quartz</artifactId>
79 <version>${shiro.version}</version>
80 </dependency>
81 <!-- shiro -->
82
83 <!-- log start -->
84 <dependency>
85 <groupId>log4j</groupId>
86 <artifactId>log4j</artifactId>
87 <version>${log4j.version}</version>
88 </dependency>
89 <dependency>
90 <groupId>org.slf4j</groupId>
91 <artifactId>slf4j-api</artifactId>
92 <version>${slf4j.version}</version>
93 </dependency>
94 <dependency>
95 <groupId>org.slf4j</groupId>
96 <artifactId>slf4j-log4j12</artifactId>
97 <version>${slf4j.version}</version>
98 </dependency>
99 <!-- log end -->
100
101 <dependency>
102 <groupId>commons-logging</groupId>
103 <artifactId>commons-logging</artifactId>
104 <version>1.1.3</version>
105 </dependency>
106
107 <dependency>
108 <groupId>javax.servlet</groupId>
109 <artifactId>jstl</artifactId>
110 <version>${jstl.version}</version>
111 </dependency>
112
113 <dependency>
114 <groupId>javax.servlet</groupId>
115 <artifactId>javax.servlet-api</artifactId>
116 <version>4.0.1</version>
117 </dependency>
118
119 <!--Spring -->
120 <dependency>
121 <groupId>org.springframework</groupId>
122 <artifactId>spring-core</artifactId>
123 <version>${spring.version}</version>
124 </dependency>
125
126 <dependency>
127 <groupId>org.springframework</groupId>
128 <artifactId>spring-webmvc</artifactId>
129 <version>${spring.version}</version>
130 </dependency>
131
132 <dependency>
133 <groupId>org.springframework</groupId>
134 <artifactId>spring-context</artifactId>
135 <version>${spring.version}</version>
136 </dependency>
137
138 <dependency>
139 <groupId>org.springframework</groupId>
140 <artifactId>spring-web</artifactId>
141 <version>${spring.version}</version>
142 </dependency>
143
144 <dependency>
145 <groupId>org.springframework</groupId>
146 <artifactId>spring-jdbc</artifactId>
147 <version>${spring.version}</version>
148 </dependency>
149
150 <dependency>
151 <groupId>org.springframework</groupId>
152 <artifactId>spring-test</artifactId>
153 <version>${spring.version}</version>
154 </dependency>
155 <!--Spring end -->
156
157 <dependency>
158 <groupId>org.mybatis</groupId>
159 <artifactId>mybatis</artifactId>
160 <version>3.5.2</version>
161 </dependency>
162
163 <dependency>
164 <groupId>org.mybatis</groupId>
165 <artifactId>mybatis-spring</artifactId>
166 <version>2.0.2</version>
167 </dependency>
168 </dependencies>
169
170 <build>
171 <finalName>shiro</finalName>
172 <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
173 <plugins>
174 <plugin>
175 <artifactId>maven-clean-plugin</artifactId>
176 <version>3.1.0</version>
177 </plugin>
178 <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
179 <plugin>
180 <artifactId>maven-resources-plugin</artifactId>
181 <version>3.0.2</version>
182 </plugin>
183 <plugin>
184 <artifactId>maven-compiler-plugin</artifactId>
185 <version>3.8.0</version>
186 </plugin>
187 <plugin>
188 <artifactId>maven-surefire-plugin</artifactId>
189 <version>2.22.1</version>
190 </plugin>
191 <plugin>
192 <artifactId>maven-war-plugin</artifactId>
193 <version>3.2.2</version>
194 </plugin>
195 <plugin>
196 <artifactId>maven-install-plugin</artifactId>
197 <version>2.5.2</version>
198 </plugin>
199 <plugin>
200 <artifactId>maven-deploy-plugin</artifactId>
201 <version>2.8.2</version>
202 </plugin>
203 </plugins>
204 </pluginManagement>
205 </build>
206 </project>
3.配置web.xml。

1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns="http://java.sun.com/xml/ns/javaee" 4 xmlns:web="http://java.sun.com/xml/ns/javaee" 5 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> 6 7 <!-- spring的配置文件--> 8 <context-param> 9 <param-name>contextConfigLocation</param-name> 10 <param-value> 11 classpath:applicationContext.xml 12 </param-value> 13 </context-param> 14 <listener> 15 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 16 </listener> 17 18 19 <!-- spring mvc核心:分发servlet --> 20 <servlet> 21 <servlet-name>mvc-dispatcher</servlet-name> 22 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 23 <!-- spring mvc的配置文件 --> 24 <init-param> 25 <param-name>contextConfigLocation</param-name> 26 <param-value>classpath:springMVC.xml</param-value> 27 </init-param> 28 <load-on-startup>1</load-on-startup> 29 </servlet> 30 <servlet-mapping> 31 <servlet-name>mvc-dispatcher</servlet-name> 32 <url-pattern>/</url-pattern> 33 </servlet-mapping> 34 35 <!-- Shiro配置 --> 36 <filter> 37 <filter-name>shiroFilter</filter-name> 38 <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 39 <init-param> 40 <param-name>targetFilterLifecycle</param-name> 41 <param-value>true</param-value> 42 </init-param> 43 </filter> 44 <filter-mapping> 45 <filter-name>shiroFilter</filter-name> 46 <url-pattern>/*</url-pattern> 47 </filter-mapping> 48 49 </web-app>
4.Spring配置文件
applicationContext.xml

1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:context="http://www.springframework.org/schema/context" 5 xmlns:util="http://www.springframework.org/schema/util" 6 xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd 7 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 8 http://www.springframework.org/schema/util 9 http://www.springframework.org/schema/util/spring-util.xsd"> 10 <context:component-scan base-package="com.vi.service"/> 11 12 <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> 13 <property name="driverClassName" value="com.mysql.jdbc.Driver"/> 14 <property name="url" value="jdbc:mysql://localhost:3306/shiro?characterEncoding=UTF-8&serverTimezone=UTC"/> 15 <property name="username" value="root"/> 16 <property name="password" value="root"/> 17 </bean> 18 19 <bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean"> 20 <property name="typeAliasesPackage" value="com.vi.entity"/> 21 <property name="dataSource" ref="dataSource"/> 22 <property name="mapperLocations" value="classpath:mapper/*Mapper.xml"/> 23 </bean> 24 25 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> 26 <property name="basePackage" value="com.vi.mapper"/> 27 </bean> 28 29 <!-- 提供shiro的相关配置 --> 30 <bean id="databaseRealm" class="com.vi.Realm.DatabaseRealm"/> 31 32 <!-- 配置shiro的过滤器工厂类,id- shiroFilter要和我们在web.xml中配置的过滤器一致 --> 33 <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> 34 <!-- 调用我们配置的权限管理器 --> 35 <property name="securityManager" ref="securityManager"/> 36 <!-- 配置我们的登录请求地址 --> 37 <property name="loginUrl" value="/login"/> 38 <!-- 如果您请求的资源不再您的权限范围,则跳转到/403请求地址 --> 39 <property name="unauthorizedUrl" value="/unauthorized"/> 40 <!-- 退出 --> 41 <property name="filters"> 42 <util:map> 43 <entry key="logout" value-ref="logoutFilter"/> 44 </util:map> 45 </property> 46 <!-- 权限配置 --> 47 <property name="filterChainDefinitions"> 48 <value> 49 <!-- anon表示此地址不需要任何权限即可访问 --> 50 /login=anon 51 /index=anon 52 /static/**=anon 53 /doLogout=logout 54 <!--所有的请求(除去配置的静态资源请求或请求地址为anon的请求)都要通过登录验证,如果未登录则跳到/login --> 55 /** = authc 56 </value> 57 </property> 58 </bean> 59 60 <!-- 退出过滤器 --> 61 <bean id="logoutFilter" class="org.apache.shiro.web.filter.authc.LogoutFilter"> 62 <property name="redirectUrl" value="/index"/> 63 </bean> 64 65 <!-- 会话ID生成器 --> 66 <bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/> 67 68 <!--会话Cookie模板,关闭浏览器立即失效--> 69 <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> 70 <constructor-arg value="sid"/> 71 <property name="httpOnly" value="true"/> 72 <property name="maxAge" value="-1"/> 73 </bean> 74 75 <!--会话DAO--> 76 <bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO"> 77 <property name="sessionIdGenerator" ref="sessionIdGenerator"/> 78 </bean> 79 80 <!--会话调度验证器,每30分钟执行一次验证,设定会话超时保存--> 81 <bean name="sessionValidationSchduler" 82 class="org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler"> 83 <property name="interval" value="1800000"/> 84 <property name="sessionManager" ref="sessionManager"/> 85 </bean> 86 87 <!--会话管理器--> 88 <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> 89 <!--全局会话超时时间,默认30分钟,单位毫秒--> 90 <property name="globalSessionTimeout" value="1800000"/> 91 <property name="deleteInvalidSessions" value="true"/> 92 <property name="sessionValidationSchedulerEnabled" value="true"/> 93 <property name="sessionValidationScheduler" ref="sessionValidationSchduler"/> 94 <property name="sessionDAO" ref="sessionDAO"/> 95 <property name="sessionIdCookieEnabled" value="true"/> 96 <property name="sessionIdCookie" ref="sessionIdCookie"/> 97 </bean> 98 99 <!--安全管理器--> 100 <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> 101 <property name="realm" ref="databaseRealm"/> 102 <property name="sessionManager" ref="sessionManager"/> 103 </bean> 104 105 <!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) --> 106 <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> 107 <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/> 108 <property name="arguments" ref="securityManager"/> 109 </bean> 110 111 <!--保证了Shiro内部LifeCycle函数的bean执行--> 112 <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> 113 114 </beans>
简单说一下这个配置
<context:component-scan base-package="com.vi.service"/>
扫描base-package中配置的包,在这个包下面,可以使用@Autowired对变量进行自动注入,由Spring为我们提供实例。
当然,即使是没有被扫描的包,如果某个类在applicationContext.xml中注册了,也可以使用@Autowired进行自动注入。
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
配置数据源。
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean">
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
这两个bean是对Mybatis的整合。让Mybatis可以使用我们上面配置的数据源,同时也配置了实体类,Mapper接口,Mapper.xml的位置,进而为我们提供之后需要的CRUD功能。
下面的部分就是Shiro的配置了。
<!-- 提供shiro的相关配置 -->
<bean id="databaseRealm" class="com.vi.Realm.DatabaseRealm"/>
这个是由我们自定义的Realm类,Shiro的认证和授权就是在这里进行的,代码是我们完成的,但是调用却是由Shiro自己进行。

1 <!-- 配置shiro的过滤器工厂类,id- shiroFilter要和我们在web.xml中配置的过滤器一致 --> 2 <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> 3 <!-- 调用我们配置的权限管理器 --> 4 <property name="securityManager" ref="securityManager"/> 5 <!-- 配置我们的登录请求地址 --> 6 <property name="loginUrl" value="/login"/> 7 <!-- 如果您请求的资源不再您的权限范围,则跳转到/403请求地址 --> 8 <property name="unauthorizedUrl" value="/unauthorized"/> 9 <!-- 退出 --> 10 <property name="filters"> 11 <util:map> 12 <entry key="logout" value-ref="logoutFilter"/> 13 </util:map> 14 </property> 15 <!-- 权限配置 --> 16 <property name="filterChainDefinitions"> 17 <value> 18 <!-- anon表示此地址不需要任何权限即可访问 --> 19 /login=anon 20 /index=anon 21 /static/**=anon 22 /doLogout=logout 23 <!--所有的请求(除去配置的静态资源请求或请求地址为anon的请求)都要通过登录验证,如果未登录则跳到/login --> 24 /** = authc 25 </value> 26 </property> 27 </bean>
这个shiroFilter的命名必须和web.xml中的shiroFilter过滤器保持一致。
这里详细配置了我们需要的权限,登录请求地址,错误跳转地址等。同时还引用了securityManager
<!--安全管理器-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="databaseRealm"/>
<property name="sessionManager" ref="sessionManager"/>
</bean>
securityManager中配置了两个属性,databaseRealm,用于认证和授权,另一个则是sessionManager,这个xml中有很多bean都是会话管理器的成员,这个具体的作用我还不清楚T_T。
<!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
<property name="arguments" ref="securityManager"/>
</bean>
这一步的作用,相当于帮我们写了:
DefaultSecurityManager securityManager = new DefaultSecurityManager(); SecurityUtils.setSecurityManager(securityManager);
做了准备环境的工作。
5.
springMVC.xml
a. springmvc的基本配置
b. 增加了对shiro的支持
这样可以在控制器Controller上,使用像@RequireRole 这样的注解,来表示某个方法必须有相关的角色才能访问
c. 指定了异常处理类DefaultExceptionHandler,这样当访问没有权限的资源的时候,就会跳到统一的页面去显示错误信息

1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:context="http://www.springframework.org/schema/context" 5 xmlns:mvc="http://www.springframework.org/schema/mvc" 6 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 7 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd 8 http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd"> 9 <context:component-scan base-package="com.vi.controller"> 10 <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> 11 </context:component-scan> 12 13 <mvc:annotation-driven/> 14 15 <mvc:default-servlet-handler/> 16 17 <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> 18 <property name="prefix" value="/WEB-INF/jsp/"/> 19 <property name="suffix" value=".jsp"/> 20 </bean> 21 22 <!--启用shiro注解--> 23 <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"> 24 <property name="proxyTargetClass" value="true"/> 25 </bean> 26 <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> 27 <property name="securityManager" ref="securityManager"/> 28 </bean> 29 30 <!--控制器异常处理--> 31 <bean id="exceptionHandlerExceptionResolver" class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver"> 32 33 </bean> 34 35 <bean class="com.vi.exception.DefaultExceptionHandler"/> 36 </beans>
6.log4j.properties
显示数据库的SQL
# Global logging configuration log4j.rootLogger=ERROR, stdout # MyBatis logging configuration... log4j.logger.com.how2java=TRACE # Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
7.
PageController.java
因为使用 ssm,所以jsp通常都会放在WEB-INF/jsp 下面,而这个位置是无法通过浏览器直接访问的,所以就会专门做这么一个类,便于访问这些jsp。
比如要访问WEB-INF/jsp/index.jsp文件,那么就通过/index 这个路径来访问。
这个类还有两点需要注意:
1. /login 只支持get方式。 post方式是后续用来进行登录行为的,这里的get方式仅仅用于显示登录页面
2. 权限注解:
通过注解: @RequiresRoles("productManager") 指明了 访问 deleteProduct 需要角色"productManager"
通过注解:@RequiresPermissions("deleteOrder") 指明了 访问 deleteOrder 需要权限"deleteOrder"

1 /**
2 * 专门用于显示页面的控制器
3 */
4 @Controller
5 @RequestMapping("")
6 public class PageController {
7
8 @RequestMapping("/index")
9 public String index() {
10 return "index";
11 }
12
13 @RequiresRoles("productManager")
14 @RequestMapping("/deleteProduct")
15 public String deleteProduct() {
16 return "deleteProduct";
17 }
18
19 @RequestMapping("/listProduct")
20 public String listProduct(){
21 return "listProduct";
22 }
23
24 @RequiresPermissions("deleteOrder")
25 @RequestMapping("/deleteOrder")
26 public String deleteOrder() {
27 return "deleteOrder";
28 }
29 @RequestMapping(value = "/login",method = RequestMethod.GET)
30 public String login() {
31 return "login";
32 }
33
34 @RequestMapping("/unauthorized")
35 public String noperms() {
36 return "unauthorized";
37 }
38 }
8.jsp
index.jsp, listProduct.jsp,deleteOrder.jsp,login.jsp,unauthorized.jsp
9.User相关
User.java,UserMapper.java,UserMapper.xml,UserService.java,UserServiceImpl.java
10.Role相关
Role.java,RoleMapper.java,RoleMapper.xml,RoleService.java,RoleServiceImpl.java
10.Permission相关
Permission.java,PermissionMapper.java,PermissionMapper.xml,PermissionService.java,PermissionServiceImpl.java
11.LoginController.java

1 @Controller
2 public class LoginController {
3
4 /**
5 * 登录方法
6 * @param name
7 * @param password
8 * @return
9 */
10 @RequestMapping(value = "/login", method = RequestMethod.POST)
11 public String login(String name, String password, Model model) {
12 UsernamePasswordToken token = new UsernamePasswordToken(name, password);
13 Subject subject = SecurityUtils.getSubject();
14 try {
15 subject.login(token);
16 Session session = subject.getSession();
17 session.setAttribute("subject", subject);
18 return "redirect:index";
19 } catch (AuthenticationException e) {
20 //认证失败
21 model.addAttribute("error", "登录失败");
22 return "login";
23 }
24 }
25 }
12.DefaultExceptionHandler.java
最后是异常处理,当发生UnauthorizedException 异常的时候,就表示访问了无授权的资源,那么就会跳转到unauthorized.jsp,而在unauthorized.jsp 中就会把错误信息通过变量 ex 取出来。
DefaultExceptionHandler 的使用,是声明在 springMVC.xml 的最后几行
/**
* 对异常进行统一的处理
*/
@ControllerAdvice
public class DefaultExceptionHandler {
@ExceptionHandler({UnauthorizedException.class})
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ModelAndView processUnauthenticatedExcetpion(NativeWebRequest request, UnauthorizedException e) {
ModelAndView mv = new ModelAndView();
mv.addObject("ex", e);
mv.setViewName("unauthorized");
return mv;
}
}
