在实际项目中,每一个用户角色登录进系统看到的菜单可能会不一样,这样就涉及到了用户权限的问题,Shiro也是目前常用的权限框架。
源码下载
shiro支持三种授权方式:
1、编程式(基本不用):通过写if/else授权代码块完成。
2、注解式:通过在执行的Java方法上防止相应的注解完成,没有权限将抛出相应的异常。
3、JSP标签:在JSP页面通过相应的标签完成。
1、rest:例子/admins/user/**=rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method] ,其中method为post,get,delete等。
2、port:例子/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString是你访问的url里的?后面的参数。
3、perms:例子/admins/user/**=perms[user:add:*],perms参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。
4、roles:例子/admins/user/**=roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如/admins/user/**=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。
5、anon:例子/admins/**=anon 没有参数,表示可以匿名使用。
6、authc:例如/admins/user/**=authc表示需要认证才能使用,没有参数
7、authcBasic:例如/admins/user/**=authcBasic没有参数表示httpBasic认证
8、ssl:例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为https
9、user:例如/admins/user/**=user没有参数表示必须存在用户,当登入操作时不做检查
这些过滤器分为两组,一组是认证过滤器,一组是授权过滤器。其中anon,authcBasic,auchc,user是第一组,
perms,roles,ssl,rest,port是第二组。
使用shiro标签之前要在jsp中引入标签库
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags"%>
shiro的几种标签:
shiro:guest:用户没有身份验证时显示的信息 ,即游客访问信息
shiro:user:用户登陆后显示的信息,通过RememberMe认证通过
shiro:principal:显示用户身份信息
shiro:authenticated:用户已经身份验证通过,即通过Subject.login登录成功,不是RememberMe登录的
shiro:notAuthenticated:用户未进行身份验证,即没有通过Subject.login登录,包括RememberMe自动登录的也属于未进行身份验证
shiro:hasRole name="admin":如果当前Subject有角色,将显示body体内容
shiro:hasAnyRoles name="admin,user":只要有body体内有任意一个角色,就会显示里面的内容
shiro:lacksRole name="admin":如果当前的Subject没有角色将显示标签内内容
shiro:hasPermission name="user:create":如果当前Subject有权限就显示标签里的内容
shiro:lacksPermission name="org:create":如果当前Subject没有权限将显示标签里的内容
权限注解可以用在service和controller层
1、@RequiresAuthentication:表示当前Subject已经通过login进行了身份验证;即Subject.isAuthenticated()返回true
2、@RequiresUser:表示当前Subject已经身份验证或者通过RemmberMe登录的
3、@RequiresGuest:表示当前Subject没有身份验证或通过RemmberMe登录过,即游客身份
4、@RequiresRole(value={"admin","user"},logical=Logical.AND):表示当前Subject需要角色admin和Uuser
5、RequiresPermissions(value={"user:a","user:b"},logical=Logical.OR):表示当前Subject需要权限user:a或user:b
1、在web.xml中配置Spring、springmvc、shiro。
shiro-2
index.jsp
contextConfigLocation
classpath:applicationContext.xml
org.springframework.web.context.ContextLoaderListener
spring
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:spring-servlet.xml
1
spring
/
shiroFilter
org.springframework.web.filter.DelegatingFilterProxy
targetFilterLifecycle
true
shiroFilter
/*
2、配置springmvc配置文件:spring-servlet.xml
3、配置IOC容器:applicationContext.xml
4、创建Realm并要在Spring IOC容器中配置,这里会用了两个Realm,当然可以使用更多个。Realm认证策略参照:Shiro——认证
package com.mfc.realm;
import java.util.HashSet;
import java.util.Set;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
/**
* @author 74790
* 1.授权需要继承AuthorizingRealm,并实现doGetAuthorizationInfo 方法
2.AuthorizingRealm类继承自AuthenticatingRealm,但没有实现AuthenticatingRealm中的
doGetAuthenticationInfo,所以认证和授权只需要继承AuthorizingRealm就可以了,同时实现他的
两个抽象方法。
*/
public class ShiroRealm extends AuthorizingRealm {
// 认证的方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("[ShiroRealm] doGetAuthenticationInfo");
//1、把AuthenticationToken转化为UsernamePasswordToken
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
//2、UsernamePasswordToken中获取Username
String username = upToken.getUsername();
//3、调用数据库方法,从数据库中查询username对用的用户记录
System.out.println("从数据库中获取username:" + username + " 所对应的用户信息");
//4、若用户不存在,可以抛出UnknownAccountException异常
if("unknown".equals(username)){
throw new UnknownAccountException("用户不存在");
}
//5、根据用户信息的情况,决定是否需要抛出其他AuthenticationException异常
if("monster".equals(username)){
throw new LockedAccountException("用户被锁定");
}
//6、根据用户的情况来构建AuthenticationInfo对象并返回,通常使用的实现类是SimpleAuthenticationInfo
//以下信息是从数据库中获取的
//principal:认证的实体信息,可以是username,也可以是数据表对应的用户的实体类对象
Object principal = username;
//credentials:密码
Object credentials = null;
if("admin".equals(username)){
credentials = "038bdaf98f2037b31f1e75b5b4c9b26e";
}else if("user".equals(username)){
credentials = "098d2c478e9c11555ce2823231e02ec1";
}
//realmName:当前realm 对象的name,调用父类的getName()方法即可
String realmName = getName();
//盐值,防止两用户密码一样的时候不安全
ByteSource credentialsSalt = ByteSource.Util.bytes(username);
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
return info;
}
// 授权需要实现的方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//1.从PrincipalCollection中获取登陆用户的信息
Object principal = principals.getPrimaryPrincipal();
//2.利用登陆用户的信息来获取当前用户的角色和权限(可能需要查询数据库)
Set roles = new HashSet();
roles.add("user");
if("admin".equals(principal)){
roles.add("admin");
}
//3.创建SimpleAuthorizationInfo对象,并设置其roles
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
//4.返回SimpleAuthorizationInfo对象
return info;
}
public static void main(String[] args) {
String hashIterations = "MD5";
Object credentials = "123456";
Object salt = ByteSource.Util.bytes("user");;
int algorithmName = 1024;
Object result = new SimpleHash(hashIterations, credentials, salt, algorithmName);
System.out.println("result:" + result);
}
}
package com.mfc.realm;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.util.ByteSource;
/**
* @author 74790
* 认证的Realm
*/
public class SecondRealm extends AuthenticatingRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("[SecondRealm] doGetAuthenticationInfo");
//1、把AuthenticationToken转化为UsernamePasswordToken
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
//2、UsernamePasswordToken中获取Username
String username = upToken.getUsername();
//3、调用数据库方法,从数据库中查询username对用的用户记录
System.out.println("从数据库中获取username:" + username + " 所对应的用户信息");
//4、若用户不存在,可以抛出UnknownAccountException异常
if("unknown".equals(username)){
throw new UnknownAccountException("用户不存在");
}
//5、根据用户信息的情况,决定是否需要抛出其他AuthenticationException异常
if("monster".equals(username)){
throw new LockedAccountException("用户被锁定");
}
//6、根据用户的情况来构建AuthenticationInfo对象并返回,通常使用的实现类是SimpleAuthenticationInfo
//以下信息是从数据库中获取的
//principal:认证的实体信息,可以是username,也可以是数据表对应的用户的实体类对象
Object principal = username;
//credentials:密码
//Object credentials = "fc1709d0a95a6be30bc5926fdb7f22f4";
Object credentials = null;
if("admin".equals(username)){
credentials = "ce2f6417c7e1d32c1d81a797ee0b499f87c5de06";
}else if("user".equals(username)){
credentials = "073d4c3ae812935f23cb3f2a71943f49e082a718";
}
//realmName:当前realm 对象的name,调用父类的getName()方法即可
String realmName = getName();
//SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, realmName);
//盐值
ByteSource credentialsSalt = ByteSource.Util.bytes(username);
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
return info;
}
public static void main(String[] args) {
String hashIterations = "SHA1";
Object credentials = "123456";
Object salt = ByteSource.Util.bytes("admin");;
int algorithmName = 1024;
Object result = new SimpleHash(hashIterations, credentials, salt, algorithmName);
System.out.println("result:" + result);
}
}
5、测试注解配置权限的service:关于注解的使用可以看上面的四
package com.mfc.service;
import java.util.Date;
import org.apache.shiro.authz.annotation.RequiresRoles;
/**
* @author 74790
* Shiro的注解方式配置在service或者是Controller中
*/
public class ShiroService {
/*
* 只有admin角色才可以访问这个方法
* */
@RequiresRoles({"admin"})
public void test(){
System.out.println("ShiroService测试权限:" + new Date());
}
}
6、Controller:
package com.mfc.ctrl;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.mfc.service.ShiroService;
@Controller
@RequestMapping(value = "shiroCtrl")
public class ShiroCtrl {
@Autowired
private ShiroService shiroService;
@RequestMapping(value = "test")
public String test(){
shiroService.test();
return "redirect:/list.jsp";
}
@RequestMapping(value = "login")
public String login(@RequestParam("username")String username,
@RequestParam("password")String password, Model model){
Subject currentUser = SecurityUtils.getSubject();
// 测试当前的用户是否已经被认证,即是否已经登录
// 调用Subject 的isAuthenticated()
if (!currentUser.isAuthenticated()) {
// 把用户名和密码封装为UsernamePasswordToken 对象
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// RememberMe
token.setRememberMe(true);
try {
// 执行登录
// 这里的token其实传到了ShiroRealm.doGetAuthenticationInfo(AuthenticationToken token)
currentUser.login(token);
}catch (AuthenticationException ae) {
// 所有认证时异常的父类
System.err.println("登录失败:"+ae.getMessage());
}
}
return "redirect:/list.jsp";
}
}
7、从数据库中获取权限配置资源的bean:这个bean需要在Spring IOC容器中注入,可以查看上面的applicationContext.xml配置文件。
package com.mfc.factory;
import java.util.LinkedHashMap;
/**
* @author 74790
* 使用bean配置的方式,将权限配置配置到数据库里面去
*/
public class FilterChainDefinitionMapBuilder {
public LinkedHashMap buildFilterChainDefinitionMapBuilder(){
LinkedHashMap map = new LinkedHashMap();
/*
*
这些可以存到数据库里面去,在这里取出来即可,注意放进map的顺序
/login.jsp = anon
/shiroCtrl/login = anon
/shiroCtrl/logout = logout
/user.jsp = roles[user]
/admin.jsp = roles[admin]
# everything else requires authentication:
/** = authc
* */
map.put("/login.jsp", "anon");
map.put("/shiroCtrl/login", "anon");
map.put("/shiroCtrl/logout", "logout");
map.put("/user.jsp", "roles[user]");
map.put("/admin.jsp", "roles[admin]");
map.put("/**", "authc");
return map;
}
}
8、JSP代码这里省略。