简单的身份验证
- principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个 principals,但只有一个 Primary principals,一般是用户名/密码/手机号。
- credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。
- 最常见的 principals 和 credentials 组合就是用户名/密码了。
shiro.ini 准备身份/凭证
[users]部分指定用户名/密码及其角色;
[roles]部分指定角色即权限信息;
#定义用户
[users]
zhang=12345
wang=abc
package ini_shiro;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
public class SimpleTest {
public static void main(String[] args) {
/* 1. 获取SecurityManager工厂,通过ini文件 */
Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:simpleshiro.ini");
//2.获得安全管理者实例,放入SecurityUtils(全局设置,一次即可)
org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//3.得到Subject,自动绑定到当前线程(web项目要 在请求结束时解除绑定)得到当前用户的身份/凭证,
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhang","12345");
try {
//4.进行身份验证
subject.login(token);//委托 SecurityManager.login
}catch(AuthenticationException e) {
System.out.println("身份验证失败");
}
if(subject.isAuthenticated()) {//为true表示验证成功
System.out.println("验证成功");
subject.logout();//身份登出
System.out.println("登出");
}
}
}
更改密码,
可捕 获 AuthenticationException 或 其 子 类 , 常 见 的 如 :
DisabledAccountException(禁用的帐号)、LockedAccountException(锁定的帐号)、
UnknownAccountException(错误的帐号)、ExcessiveAttemptsException(登录失败次数过
多)、IncorrectCredentialsException (错误的凭证)、ExpiredCredentialsException(过期的
凭证)等,具体请查看其继承关系;对于页面的错误消息展示,最好使用如“用户名/密码
错误”而不是“用户名错误”/“密码错误”,防止一些恶意用户非法扫描帐号库;
shrio.ini
#定义安全相关的数据
#用户
[users]
zhang3 =12345,admin
li4=abcde,productManager
#角色和权限
[roles]
#admin什么都能做
admin=*
#productManager做Product相关
productManager = addProduct,deleteProduct,updateProduct,selectProduct
#orderManager做Product相关
orderManager = addOrder,deleteOrder,updateOrder,selectOrder
User
package ini_shiro;
public class User {
private int id;
private String name;
private String password;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public User(String name, String password) {
super();
this.name = name;
this.password = password;
}
public User() {
super();
}
}
Test
package ini_shiro;
import java.util.ArrayList;
import java.util.List;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.apache.shiro.mgt.SecurityManager;
public class Test {
public static void main(String[] args) {
/**
* 准备三个用户,两个定义了,一个没有
*/
User zhang3 = new User();
zhang3.setName("zhang3");
zhang3.setPassword("12345");
User li4 = new User();
li4.setName("li4");
li4.setPassword("abcde");
User wang5 = new User("wang5","wrong");//没有定义在shiro.ini中
List<User> users = new ArrayList<>();
users.add(zhang3);
users.add(li4);
users.add(wang5);
//定义角色
String roleAdmin = "admin";
String roleProductManager ="productManager";
List<String> roles = new ArrayList<>();
roles.add(roleAdmin);
roles.add(roleProductManager);
//定义权限
String permitProduct = "addProduct";
String permitOrder = "addOrder";
List<String> permits = new ArrayList<>();
permits.add(permitProduct);
permits.add(permitOrder);
//登陆每个用户
for (User user : users) {
if(login(user))
System.out.printf("%s \t登陆成功,用的密码是 %s\t %n",user.getName(),user.getPassword());
else
System.out.printf("%s \t登录失败,用的密码是 %s\t %n",user.getName(),user.getPassword());
}
System.out.println("----分割线------");
//判断能够登录的用户是否拥有某个角色
for (User user : users) {
for (String role : roles) {
if(login(user)) {
if(hasRole(user, role))
System.out.printf("%s\t 拥有角色: %s\t%n",user.getName(),role);
else
System.out.printf("%s\t 不拥有角色: %s\t%n",user.getName(),role);
}
}
}
System.out.println("------ 分割线------");
//判断能够登录的用户,是否拥有某种权限
for(User user:users) {
for(String role:roles) {
for(String permit:permits) {
if(login(user)) {
if(hasRole(user, role)) {
if(isPermitted(user, permit)) {
System.out.printf("%s\t 拥有权限:%s ",user.getName(),permit);
}
else
System.out.printf("%s\t 没有权限:%s ",user.getName(),permit);
}
}
}
}
}
}
//获取当前用户
private static Subject getSubject(User user) {
//加载配置文件,并获取工厂
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//获取安全管理者实例
SecurityManager sm = factory.getInstance();
//将安全管理者放入全局对象
SecurityUtils.setSecurityManager(sm);
//全局对象通过安全管理者生成Subject对象
Subject subject = SecurityUtils.getSubject();
return subject;
}
//是否有角色
private static boolean hasRole(User user,String role) {
Subject subject = getSubject(user);
return subject.hasRole(role);
}
//是否有权限
private static boolean isPermitted(User user,String permit) {
Subject subject = getSubject(user);
return subject.isPermitted(permit);
}
private static boolean login(User user) {
Subject subject = getSubject(user);
if(subject.isAuthenticated()) {
subject.logout();
}
UsernamePasswordToken token = new UsernamePasswordToken(user.getName(),user.getPassword());
try {
//将用户的数据token 最终传递到Realm中进行对比
subject.login(token);
}catch(AuthenticationException e) {
//e.printStackTrace();//验证错误
return false;
}
return subject.isAuthenticated();//身份是否验证
}
}
值得改进的地方:
1. 用户名/密码硬编码在 ini 配置文件,以后需要改成如数据库存储,且密码需要加密;
2、用户身份 Token 可能不仅仅是用户名/密码,也可能还有其他的,如登录时允许用户名/
邮箱/手机号同时登录
subject.isAuthenticated() 和subject.isRememebered区别
shiro 维护了这种状态,
假设你使用卓越网,你已经成功登录并且在购物蓝中添加了一些书籍,但你由于临时要参加一个会议,匆忙中你忘记退出登录,当会议结束,回家的时间到了,于是你离开了办公室。
第二天当你回到工作,你意识到你没有完成你的购买动作,于是你回到卓越网,这时,卓越网“记得”你是认证,通过你的名字向你打招呼,仍旧给你提供个性化的图书推荐,对于卓越,subject.isRemembered()将返回真。
但是当你想访问你帐号的信用卡信息完成图书购买的时候会怎样呢?虽然卓越“记住”了你(isRemembered() == true),它不能担保你就是你(也许是正在使用你计算机的同事)。
于是,在你执行像使用信用卡信息之类的敏感操作之前,卓越强制你登录以使他们担保你的身份,在你登录之后,你的身份已经被验证,对于卓越,isAuthenticated()将返回真。
这类情景经常发生,所以shiro加入了该功能,你可以在你的程序中使用。现在是使用isRemembered()还是使用isAuthenticated()来定制你的视图和工作流完全取决于你自己,但shiro维护这种状态基础以防你可能会需要。