Shiro权限管理框架
一. 简介
功能简介:
Shiro架构
二. 运行Shiro工程
创建Maven工程
pom文件如下:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.1</version>
</dependency>
建立日志配置文件
# For all other servers: Comment out the Log4J listener in web.xml to activate Log4J.
log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
#begin
#for normal test,delete when online
log4j.logger.com.ibatis=DEBUG
log4j.logger.com.ibatis.common.jdbc.SimpleDataSource=DEBUG
log4j.logger.com.ibatis.common.jdbc.ScriptRunner=DEBUG
log4j.logger.com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate=DEBUG
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
编写shiro配置文件
[users]
root=123456,admin
test=123456,role1,role2
[roles]
admin=*
role1=index.html,user.html
role2=user.html,menu.html
;anon 不进行任何验证
;authc 必须登录后才能访问
[urls]
/login.html=anon
/index.html=authc
/role.html=authc,roles[admin]
/menu/**=authc,roles[admin],perms[menu:*]
编写测试类(账号信息存在于配置文件中)
public class ShiroTest {
public static void main(String[] args) {
Factory<SecurityManager> factory= new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager=factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject currentUser=SecurityUtils.getSubject();
System.out.println("是否登录"+currentUser.isAuthenticated());
UsernamePasswordToken token=new UsernamePasswordToken("root","123456");
try {
currentUser.login(token);
} catch (IncorrectCredentialsException e) {
System.out.println("密码不正确");
}catch (UnknownAccountException e) {
System.out.println("用户名不存在");
}
System.out.println("登录");
System.out.println("用户是否拥有admin角色"+currentUser.hasRole("admin"));
System.out.println("用户能否访问url"+currentUser.isPermitted("index.html"));
System.out.println("是否登录"+currentUser.isAuthenticated());
}
}
编写测试类(账号信息存于数据库中,需要查库验证)
Pom文件中加入:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.11.RELEASE</version>
</dependency>
新的配置文件如下
[main]
dataSource=org.springframework.jdbc.datasource.DriverManagerDataSource
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://127.0.0.1:3306/test
dataSource.username=root
#如果数据库没有密码,就不要写这行
dataSource.password=123456
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
#是否检查权限
jdbcRealm.permissionsLookupEnabled = true
jdbcRealm.dataSource=$dataSource
#重写sql语句
#根据用户名查询出密码
jdbcRealm.authenticationQuery = select PASSWORD from SHIRO_USER where USER_NAME = ?
#根据用户名查询出角色
jdbcRealm.userRolesQuery = select ROLE_NAME from SHIRO_USER_ROLE where USER_NAME = ?
#根据角色名查询出权限
jdbcRealm.permissionsQuery = select PERM_NAME from SHIRO_ROLE_PERMISSION WHERE ROLE_NAME = ?
securityManager.realms=$jdbcRealm
为什么重写sql
查看负责与数据库进行交互的Realm,我们发现其中sql语句已经定义好了,而且此处sql的结构不能改变。例如select password from users where username = ?,无论字段名,表名怎么变,jdbcrealm都是当作从用户表里按照用户名查密码,即使你想按照ID查密码,它仍然当作用户名去查。
重写sql的意义在于,你可以按照自己的表名,字段名去定义这段sql,虽然不能更改sql的逻辑
我的数据库建表情况如下:
表中数据如下:
测试类如下:
public class ShiroTestFromJdbc {
public static void main(String[] args) {
Factory<SecurityManager> factory= new IniSecurityManagerFactory("classpath:shiro-mysql.ini");
SecurityManager securityManager=factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject currentUser=SecurityUtils.getSubject();
System.out.println("是否登录"+currentUser.isAuthenticated());
UsernamePasswordToken token=new UsernamePasswordToken("admin","123456");
try {
currentUser.login(token);
} catch (IncorrectCredentialsException e) {
System.out.println("密码不正确");
}catch (UnknownAccountException e) {
System.out.println("用户名不存在");
}
System.out.println("登录");
System.out.println("用户是否拥有admin角色"+currentUser.hasRole("admin"));
System.out.println("用户能否访问url"+currentUser.isPermitted("index.html"));
System.out.println("是否登录"+currentUser.isAuthenticated());
}
}
接下来,我们将shiro与web工程进行整合,并自己编写realm(上述代码使用的是jdbcrealm)
pom文件中加入:
<!--shiro web类库-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
web.xml文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<context-param>
<param-name>shiroEnvironmentClass</param-name>
<param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value>
</context-param>
<!-- 此处用于加载shiro的配置文件-->
<context-param>
<param-name>shiroConfigLocations</param-name>
<param-value>classpath:shiro-mysql.ini</param-value>
</context-param>
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>*.html</url-pattern>
</filter-mapping>
</web-app>
在配置文件中注入自定定义的realm
myRealmAR=com.hd.shiro.MyRealmAuthorizing
securityManager.realms=$myRealmAR
public class MyRealmAuthorizing extends AuthorizingRealm {
@Override
public String getName() {
return "MyRealmAR";
}
/**
* 用于授权
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println(principals.getPrimaryPrincipal());
//根据主键去查询用户权限
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.addRole("admin");
authorizationInfo.addRole("test");
authorizationInfo.addStringPermission("index.html");
return authorizationInfo;
}
/**
*
* @param token
* @return 用于登录
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken usertoken = (UsernamePasswordToken) token;
System.out.println(usertoken.getUsername());
//根据用户名查询数据库,找到用户真正的密码
if("admin".equals(usertoken.getUsername())){
return new SimpleAuthenticationInfo("admin","123456",getName());
}else{
throw new UnknownAccountException(); //如果用户名错误
}
}
}
具体源码地址: https://gitee.com/hdyxk/shiro-framework-learning.
注意:
一.
/=authc
/login.html=anon
/index.html = authc
/role.html=authc,roles[admin]
/menu/**=authc,roles[admin,test],perms[menu:*]
/menu/**同时需要角色和权限都满足才可以访问,但实际中我们希望只要角色和权限有一个满足即可,我们只需重写自己的权限过滤器即可
public class MyPermFilter extends AuthorizationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object mappedValue) throws Exception {
String[] perms = (String[]) mappedValue;
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
for (String p : perms) {
if (subject.isPermitted(p)) {
session.setAttribute("Allowed", true);
return true;
}
}
return false;
}
}
public class MyRoleFilter extends AuthorizationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object mappedValue) throws Exception {
String[] roles = (String[]) mappedValue;
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
if (session.getAttribute("Allowed") != null) {
return true;
}
for (String role : roles) {
if (subject.hasRole(role)) {
return true;
}
}
return false;
}
}
在shirofilter的bean中加入:
</property>
<property name="filters">
<map>
<entry key="roles">
<bean class="filter.MyRoleFilter"/>
</entry>
<entry key="perms">
<bean class="filter.MyPermFilter"/>
</entry>
</map>
</property>
二.
@RequiresPermissions("menu:edit")
@RequestMapping("/menu/list.html")
public String list() {
return "menu";
}
使用注解可是进行细粒度的权限控制,比如访问呢list.html必须拥有menu.edit权限。
三. 如果权限认真失败,我们可以设置增强,对异常封装,返回指定信息
@ControllerAdvice
public class AuthExceptionHandler {
@ExceptionHandler({
UnauthorizedException.class})
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ModelAndView processUnauthenticatedException(NativeWebRequest request, UnauthorizedException e) {
ModelAndView mv = new ModelAndView();
mv.addObject("exception", e.getMessage());
mv.setViewName("error");
return mv;
}
}
四. 在shiro过滤器中,匹配规则在实际中是存在数据库中的。为此我们需要重写ShiroFilterFactoryBean
<value>
/=authc
/login.html=anon
/index.html = authc
/role.html=authc,roles[admin]
/menu/**=authc,roles[admin,test],perms[menu:*]
</value>
但是只是重写设置匹配规则的方法即可
public class MyShiroFilterFactoryBean extends ShiroFilterFactoryBean {
@Override
public void setFilterChainDefinitions(String definitions) {
Ini ini = new Ini();
ini.load(definitions);
Ini.Section section = ini.getSection("url s");
if (CollectionUtils.isEmpty(section)) {
section = ini.getSection("");
}
section.put("/menu/**","roles[admin]");
this.setFilterChainDefinitionMap(section);
}
}
/menu/**=authc,roles[admin,test],perms[menu:*]等价于section.put("/menu/**","roles[admin]");