Shiro & Spring Security - 小记
实际开发中:
一 、 shiro已经满足基本的安全需求,使用简单,需要用数据库的role和permission表来注入实现。
(1)相关注解:
@RequiresRoles("xxx") //角色控制
@RequiresPermissions("xxx") //功能权限控制
(2)配置类ShiroConfig模板 :
package com.kevin.springbootkevin1.config;
import com.kevin.springbootkevin1.realm.UserRealm;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
/**
* ShiroFilterFactoryBean 处理拦截资源文件问题。
* 注意:单独一个ShiroFilterFactoryBean配置是或报错的,以为在
* 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
* Filter Chain定义说明 1、一个URL可以配置多个Filter,使用逗号分隔 2、当设置多个过滤器时,全部验证通过,才视为通过
* 3、部分过滤器可指定参数,如perms,roles
*/
@Bean
public ShiroFilterFactoryBean shirFilter(org.apache.shiro.mgt.SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 拦截器.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
//配置静态资源允许访问
filterChainDefinitionMap.put("/user/login","anon");
filterChainDefinitionMap.put("/user/loginAction","anon");
//filterChainDefinitionMap.put("/css/**","anon");
//filterChainDefinitionMap.put("/index","anon");
// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
filterChainDefinitionMap.put("/**", "authc");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/user/login");
// 未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
Map<String, Filter> filters=new HashMap<String,Filter>();
shiroFilterFactoryBean.setFilters(filters);
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public EhCacheManager getEhCacheManager() {
EhCacheManager em = new EhCacheManager();
em.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
return em;
}
// 开启Controller中的shiro注解
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
daap.setProxyTargetClass(true);
return daap;
}
/**
* 配置org.apache.shiro.web.session.mgt.DefaultWebSessionManager
* @return
*/
@Bean
public DefaultWebSessionManager getDefaultWebSessionManager(){
DefaultWebSessionManager defaultWebSessionManager=new DefaultWebSessionManager();
defaultWebSessionManager.setSessionDAO(getMemorySessionDAO());
defaultWebSessionManager.setGlobalSessionTimeout(4200000);
defaultWebSessionManager.setSessionValidationSchedulerEnabled(true);
defaultWebSessionManager.setSessionIdCookieEnabled(true);
defaultWebSessionManager.setSessionIdCookie(getSimpleCookie());
return defaultWebSessionManager;
}
/**
* 配置org.apache.shiro.session.mgt.eis.MemorySessionDAO
* @return
*/
@Bean
public MemorySessionDAO getMemorySessionDAO(){
MemorySessionDAO memorySessionDAO=new MemorySessionDAO();
memorySessionDAO.setSessionIdGenerator(javaUuidSessionIdGenerator());
return memorySessionDAO;
}
@Bean
public JavaUuidSessionIdGenerator javaUuidSessionIdGenerator(){
return new JavaUuidSessionIdGenerator();
}
/**
* session自定义cookie名
* @return
*/
@Bean
public SimpleCookie getSimpleCookie(){
SimpleCookie simpleCookie=new SimpleCookie();
simpleCookie.setName("security.session.id");
simpleCookie.setPath("/");
return simpleCookie;
}
@Bean
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor(){
return new LifecycleBeanPostProcessor();
}
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(UserRealm userRealm) {
DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
dwsm.setRealm(userRealm);
// <!-- 用户授权/认证信息Cache, 采用EhCache 缓存 -->
dwsm.setCacheManager(getEhCacheManager());
dwsm.setSessionManager(getDefaultWebSessionManager());
return dwsm;
}
@Bean
public UserRealm userRealm(EhCacheManager cacheManager) {
UserRealm userRealm = new UserRealm();
userRealm.setCacheManager(cacheManager);
return userRealm;
}
/**
* 开启shrio注解支持
* @param userRealm
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(UserRealm userRealm){
AuthorizationAttributeSourceAdvisor aasa=new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(getDefaultWebSecurityManager(userRealm));
return aasa;
}
}
(3)realm层 重要的两个方法:
AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc):控制角色权限
AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken at) throws AuthenticationException:控制登录
注:
POM引入:
<!--Shiro安全-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>
使用方法:
1.创建数据库user、role、permission表,分别对应用户表、角色表、权限表。
2.配置ShiroConfig,如上图第(2)项,
说明:
这段代码的目的:添加允许访问的请求(使这些请求能通过,不被拦截)。
其他的根据注释、查阅资料学习。
3.书写realm层
ex:
package com.kevin.springbootkevin1.realm;
import com.kevin.springbootkevin1.bean.User;
import com.kevin.springbootkevin1.mapper.PermissionMapper;
import com.kevin.springbootkevin1.mapper.RoleMapper;
import com.kevin.springbootkevin1.service.IUserService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
/**
* <p>
* 用户权限类-持久层
* </p>
*
* @author Jia Xin
* @since 2020-02-11
*/
public class UserRealm extends AuthorizingRealm {
@Autowired
private IUserService userService;
@Autowired
private RoleMapper roleMapper;
@Autowired
private PermissionMapper permissionMapper;
//控制角色权限
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
//获取登录用户名
String username = (String)pc.getPrimaryPrincipal();
//定义一个权限管理器
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info = userService.addRolePermissions(info,username);
System.out.println("username:"+username+"," +
"角色和权限信息:" + info);
return info;
}
//控制登录
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken at) throws AuthenticationException {
AuthenticationInfo info;
//token携带的主键username
String username = (String)at.getPrincipal();
//第一步拦截-如果前端传来的为空
if(username == null) {
return null;
}
//通过用户名查询得到user实体
User user = userService.selectUserByUsername(username);
if(user != null){ //username.equals(user.getUsername())
String md5Hash = new Md5Hash(user.getPassword() , "123").toString();
info = new SimpleAuthenticationInfo(username, md5Hash, getName());
return info;
} else {
//用户名不存在
return null;
}
}
}
注:selectUserByUsername函数写在service层下的。
4.书写Controller层:
登录请求:为了验证,用了md5哈希加密。
subject.login(token); shiro的安全验证。
如果抛出异常,则:验证不成功;反之,则成功。
//登录验证
@GetMapping(value = "/loginAction")
//@RequestBody Map<String, Object> user
public String loginAction(String username ,String password) {
// String username = user.get("username").toString();
// String password = user.get("password").toString();
//添加用户认证信息
Subject subject = SecurityUtils.getSubject();
//用哈希对密码加密
String md5Hash = new Md5Hash(password, "123").toString();
//AuthenticationToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
UsernamePasswordToken token = new UsernamePasswordToken(username, md5Hash);
//System.out.println("subject.isPermitted() => " + subject.isPermitted("sys:delete"));
try {
//登录成功
subject.login(token);
return "LoginS";
} catch (Exception e) {
//登录失败
return "LoginF";
}
}
5.模拟数据:
user表
role表
permission表
zhangsan是admin角色,roleid=1,没有任何权限。
lisi是teacher角色,roleid=2,有sys:add sys:edit sys:delete 权限.
6.加注解:
这里演示第一个角色控制。
7.验证:
A. 首先lisi登陆:
他是teacher角色,没有权限。
发起getuser请求,出现异常,shiro控制进行拦截了,在意料之中。
而且springboot console控制台有记录:
2020-02-18 00:15:27.312 WARN 11764 --- [nio-8088-exec-6] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.apache.shiro.authz.UnauthorizedException: Subject does not have role [admin]]
B. 然后zhangsan登陆:
他是admin角色,三个权限。
发起getuser请求,取到结果,未被拦截,在意料之中。
8.演示到此。
二 、 Spring Security包含shiro所能完成得功能,很好地支持Spring应用,能胜任大型软件所需的安全功能需求。