本文将讲述在Spring+SpringMvc项目中使用Shiro来保障系统的安全。关于shiro理论性的东西本文不深入讲解,重点在于在Spring项目中配置和使用shiro.关于理论性的东西,可以看看 张开涛的《跟我学Shiro》。话不多说,先来看看如何配置Shiro。
1 在web.xml中添加一个ShiroFilter
shiroFilter
org.springframework.web.filter.DelegatingFilterProxy
targetFilterLifecycle
true
shiroFilter
/*
error/403
error/500
3 新建一个Spring上下文支持Shiro的配置文件 spring-context-shiro.xml
Shiro Configuration
/service/login/loginIn = anon
/service/admin/** = roles[admin]
/service/** = authc
6 由于在4中需要注册一个systemAuthorizingRealm bean .所以创建一个systemAuthorizingRealm类
/**
* Shiro 从此类 获取安全数据(如用户、角色、权限)
* 创建日期: 2017年10月24日
* @author 赵松强
*/
public class SystemAuthorizingRealm extends AuthorizingRealm{
@Autowired
private IRealmService service;
/** 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用
* @see org.apache.shiro.realm.AuthorizingRealm#doGetAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 获取当前已登录的用户
Principal principal = (Principal) getAvailablePrincipal(principals);
IUser user = service.getUserByToken(principal.getUserName(),principal.getOfficeId());
if(user == null){
return null;
}else{
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();//用户权限信息
// 添加用户权限
info.addStringPermission("user");
List list = service.getMenuList(user);
for (IMenu menu : list){
if (!"".equals(menu.getPermission())){
// 添加基于Permission的权限信息
for (String permission : menu.getPermission().split(",")){
info.addStringPermission(permission);
}
}
}
List rolesList = service.getRoleList(user);
// 添加用户角色信息
for (IRole role : rolesList){
info.addRole(role.getRoleEnName());
}
return info;
}
}
/** 用户信息认证回调函数, 登录时调用用于验证用户信息
* @see org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
//将默认登录口令转换为带officeid的口令
UsernamePasswordToken token =(UsernamePasswordToken) authcToken;
//根据用户名和officeid获取用户信息
IUser user = service.getUserByToken(token.getUsername(),token.getOfficeid());
if(user == null){
return null;
}
if(user.loginEnAble()){//用户可以登录
//返回的信息中包括了一个Principal,项目中可以通过SecurityUtils.getSubject().getPrincipal()获取该信息
return new SimpleAuthenticationInfo(new Principal(user),
user.getPassword(), getName());
}else{//如果用户已经被禁止登录
throw new AuthenticationException("msg:该帐号被禁止登录.");
}
}
/**
* 用户信息,可以通过 SecurityUtils.getSubject().getPrincipal()获取该信息
*/
public static class Principal implements Serializable {
private String number;//用户编码(工号、学号等)
private String userName;//用户名
private String officeId;//所属组织机构id
private String office;//所属组织机构
public Principal(IUser user){
this.number = user.getNumber();
this.userName = user.getUserName();
this.officeId = user.getOfficeId();
this.office = user.getOffice();
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getOfficeId() {
return officeId;
}
public void setOfficeId(String officeId) {
this.officeId = officeId;
}
public String getOffice() {
return office;
}
public void setOffice(String office) {
this.office = office;
}
}
}
7 在6中引用了一个UsernamePasswordToken登陆扣了类,这个类主要是重写shiro的UsernamePasswordToken增加officeid,用于标识用户所属组织机构id。
/**
* 重写shiro的UsernamePasswordToken增加officeid,用于标识用户所属组织机构id
* 创建日期: 2017年10月26日
* @author 赵松强
*/
public class UsernamePasswordToken extends org.apache.shiro.authc.UsernamePasswordToken {
private static final long serialVersionUID = 1L;
public UsernamePasswordToken(String officeId,String username,String password){
super.setUsername(username);
super.setPassword(password.toCharArray());
this.officeid = officeId;
}
/**
* 用户所属组织结构的id
*/
private String officeid;
public String getOfficeid() {
return officeid;
}
public void setOfficeid(String officeid) {
this.officeid = officeid;
}
public static long getSerialversionuid() {
return serialVersionUID;
}
}
8 目前为止,我们已经为shiro提供了数据源,我们可以真正使用它了 这里我新建了一个Shiro工具类,简单写了两个使用的方法
public class ShiroUtils {
/**
* 用户登录时调用登录方法
* @param officeId 组织机构id
* @param username 用户名
* @param password 密码
* @return 登录信息
* {@link com.swx.cn.rbac.bean.LoginInfo}
*/
public static LoginInfo login(String officeId,String username,String password){
UsernamePasswordToken token = new UsernamePasswordToken(officeId,username,password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
return new LoginInfo("登录成功",true);
} catch (AuthenticationException e) { //登录失败
String className = e.getClass().getName(), message = "";
if (IncorrectCredentialsException.class.getName().equals(className)
|| UnknownAccountException.class.getName().equals(className)){//用户名或密码错误导致的异常
message = "用户名或密码错误, 请重试.";
}else if (e.getMessage() != null && e.getMessage().contains("msg:")){//非用户名或密码错误导致的异常
message = e.getMessage().replace("msg:", "");
}else{//其他异常
message = "系统出现点问题,请稍后再试!";
e.printStackTrace(); // 输出到控制台
}
return new LoginInfo(message,false);
}
}
/**判断用户是否拥有指定的角色标识
* @param roleName 角色标识
* @return
*/
public static boolean hasRole(String roleName){
Subject subject = SecurityUtils.getSubject();
return subject.hasRole(roleName);
}
/**
* 获取Shiro的session (Shiro自己实现的session机制,注意不是HttpSession)
* @return Shiro session
*/
public static Session getSession(){
Subject subject = SecurityUtils.getSubject();
return subject.getSession(true);
}
}
其实这个类的主要功能都是来自于 SecurityUtils.getSubject() 这个方法返回的 Subject对象。我们可以看看org.apache.shiro.subject.Subject 接口为我们提供了那些使用的方法
public interface Subject {
Object getPrincipal();
PrincipalCollection getPrincipals();
boolean isPermitted(String permission);
boolean isPermitted(Permission permission);
boolean[] isPermitted(String... permissions);
boolean[] isPermitted(List permissions);
boolean isPermittedAll(String... permissions);
boolean isPermittedAll(Collection permissions);
void checkPermission(String permission) throws AuthorizationException;
void checkPermission(Permission permission) throws AuthorizationException;
void checkPermissions(String... permissions) throws AuthorizationException;
void checkPermissions(Collection permissions) throws AuthorizationException;
boolean hasRole(String roleIdentifier);
boolean[] hasRoles(List roleIdentifiers);
boolean hasAllRoles(Collection roleIdentifiers);
void checkRole(String roleIdentifier) throws AuthorizationException;
void checkRoles(Collection roleIdentifiers) throws AuthorizationException;
void checkRoles(String... roleIdentifiers) throws AuthorizationException;
void login(AuthenticationToken token) throws AuthenticationException;
boolean isAuthenticated();
boolean isRemembered();
Session getSession();
Session getSession(boolean create);
void logout();
V execute(Callable callable) throws ExecutionException;
void execute(Runnable runnable);
Callable associateWith(Callable callable);
Runnable associateWith(Runnable runnable);
void runAs(PrincipalCollection principals) throws NullPointerException, IllegalStateException;
boolean isRunAs();
PrincipalCollection getPreviousPrincipals();
PrincipalCollection releaseRunAs();
}
使用上面这个接口提供的功能我们基本上能满足所有我们需要shiro 的功能。
9 完成工具类后我们可以在登录方法中调用工具类中的登录方法了
@Controller
@RequestMapping("/login")
public class LoginController {
@RequestMapping("loginIn")
public String login(HttpServletRequest request, HttpServletResponse response, Model model){
LoginInfo flag = ShiroUtils.login("370000", "郑进", "11111111");
return "login";
}
}
LoginInfo类里面只是简单封装了下登录的结果
public class LoginInfo {
/**
* 登录信息 主要是承载失败信息
*/
private String message;
/**
* 登录结果 true:登录成功 false:登录失败
*/
private boolean result;
//省略setter getter
}
Spring+SpringMVC环境下使用Shiro已经搭建好了,在系统中可以在控制器的方法上使用
@RequiresPermissions("user:view:123") //标识用户必须要有 user:view:123 才能访问此方法
@RequiresRoles("ceshi") //标识用户必须要有 ceshi 角色才能访问此方法
当然也可以通过 Subject接口提供的方法在任何地方进行 权限验证