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; } }