Shiro是一个安全框架。
Subject: 代表当前正在执行操作的用户,但Subject代表的可以是人,也可以是任何第三方系统帐号。当然每个subject实例都会被绑定到SercurityManger上。
SecurityManger:SecurityManager是Shiro核心,主要协调Shiro内部的各种安全组件。
Realm: 用户数据和Shiro数据交互的桥梁。比如需要用户身份认证、权限认证。都是需要通过Realm来读取数据。
- 添加Maven依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.2.3</version>
</dependency>
- 添加自定义Realm
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import java.util.HashSet;
import java.util.Set;
public class CustomRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
Set<String> permissionList = new HashSet<>();
permissionList.add("WORKER");
simpleAuthorizationInfo.addStringPermissions(permissionList);
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
String password = new String(token.getPassword());
if ("root".equals(username) && "123456".equals(password)) {
return new SimpleAuthenticationInfo(new Object(), token.getPassword(), token.getUsername());
}
throw new RuntimeException("账号或密码不正确,请重新登陆");
}
}
- 添加shiro的配置文件
import com.example.demo.shiro.CustomRealm;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
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.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/login");
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/login", "anon"); // 登陆
filterChainDefinitionMap.put("/**", "authc");// 需要认证才可以访问
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(customRealm());
return securityManager;
}
@Bean
public CustomRealm customRealm() {
return new CustomRealm();
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
}
- 在Controller中使用shiro
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class UserController {
@RequestMapping("login")
@ResponseBody
public String login(@RequestParam("username") String username, @RequestParam("password") String password) {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken authenticationToken = new UsernamePasswordToken(username, password);
try {
subject.login(authenticationToken);
} catch (Exception exp) {
return "账号或密码不正确, 请重新登录";
}
if (subject.isAuthenticated()) {
return "登陆成功";
} else {
authenticationToken.clear();
return "登陆失败";
}
}
@RequestMapping("index")
public String toIndex() {
return "index";
}
@RequestMapping("admin")
@RequiresPermissions("WORKER")
public String toAdmin() {
return "admin";
}
}
更完整的shiro配置
import com.example.demo.shiro.CustomRealm;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.session.mgt.eis.SessionIdGenerator;
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.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import java.util.LinkedHashMap;
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager manager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(manager);
bean.setLoginUrl("/login"); // 配置登录的url和登录成功的url
bean.setSuccessUrl("/index"); // 登录成功后要跳转的链接
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
filterChainDefinitionMap.put("/css/**", "anon"); // 静态资源
filterChainDefinitionMap.put("/js/**", "anon"); // 静态资源
filterChainDefinitionMap.put("/fonts/**", "anon"); // 静态资源
filterChainDefinitionMap.put("/images/**", "anon"); // 静态资源
filterChainDefinitionMap.put("/favicon.ico", "anon"); // 静态资源
filterChainDefinitionMap.put("/", "anon"); // 登录页面
filterChainDefinitionMap.put("/login", "anon"); // 登录页面
filterChainDefinitionMap.put("/**", "authc");// 需要认证才可以访问
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return bean;
}
// 配置核心安全事务管理器
@Bean
public SecurityManager securityManager(@Qualifier("customRealm") CustomRealm authRealm,
@Value("${spring.redis.host}") String redisHost, @Value("${spring.redis.port}") String redisPort,
@Value("${spring.redis.password}") String password) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(authRealm); // 设置自定义realm.
manager.setRememberMeManager(rememberMeManager()); // 配置记住我
manager.setCacheManager(shiroCacheManager(redisHost, redisPort, password)); // 配置redis缓存
manager.setSessionManager(sessionManager(redisHost, redisPort, password)); // 配置自定义session管理,使用redis
return manager;
}
// cookie管理对象;记住我功能,rememberMe管理器
@Bean
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
cookieRememberMeManager.setCipherKey(Base64.decode("JQzBACCjs5qhy2yXSdYEmSubt4hLJEExwq3JI/YEq+U="));
return cookieRememberMeManager;
}
// cookie对象;会话Cookie模板 ,默认为: JSESSIONID 问题:
// 与SERVLET容器名冲突,重新定义为sid或rememberMe,自定义
@Bean
public SimpleCookie rememberMeCookie() {
SimpleCookie simpleCookie = new SimpleCookie("remember"); // 这个参数是cookie的名称,对应前端的checkbox的name = remember
// setcookie的httponly属性如果设为true的话,会增加对xss防护的安全系数。它有以下特点:
// 只能通过http访问,javascript无法访问
// 防止xss读取cookie
simpleCookie.setHttpOnly(true);
simpleCookie.setPath("/");
// 记住我cookie生效时间10天 ,单位秒
simpleCookie.setMaxAge(60 * 60 * 24 * 10);
return simpleCookie;
}
// shiro缓存管理器; 需要添加到securityManager中
@Bean
public RedisCacheManager shiroCacheManager(String redisHost, String redisPort, String password) {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager(redisHost, redisPort, password));
redisCacheManager.setPrincipalIdFieldName("id"); // redis中针对不同用户缓存
redisCacheManager.setExpire(60 * 10); // 用户权限信息缓存时间, 单位秒。 10分钟
return redisCacheManager;
}
@Bean
public RedisManager redisManager(String redisHost, String redisPort, String redisPassword) {
RedisManager redisManager = new RedisManager();
redisManager.setHost(String.format("%s:%s", redisHost, redisPort));
redisManager.setPassword(redisPassword);
return redisManager;
}
@Bean(name = "sessionManager")
public DefaultWebSessionManager sessionManager(String redisHost, String redisPort, String password) {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionIdCookie(sessionIdCookie());
sessionManager.setSessionDAO(sessionDAO(redisHost, redisPort, password));
sessionManager.setCacheManager(shiroCacheManager(redisHost, redisPort, password));
sessionManager.setGlobalSessionTimeout(1800000); // 全局会话超时时间 单位毫秒,默认30分钟
sessionManager.setDeleteInvalidSessions(true);// 是否开启删除无效的session对象 默认为true
sessionManager.setSessionValidationSchedulerEnabled(true); // 是否开启定时调度器进行检测过期session 默认为true
sessionManager.setSessionValidationInterval(3600000);
sessionManager.setSessionIdUrlRewritingEnabled(false); // 取消url 后面的 JSESSIONID
return sessionManager;
}
/**
* 配置保存sessionId的cookie 注意:这里的cookie 不是[记住我]的cookie, 记住我需要一个cookie session管理
* 也需要自己的cookie 默认为: JSESSIONID 问题: 与SERVLET容器名冲突,重新定义为sid
*
* @return
*/
@Bean
public SimpleCookie sessionIdCookie() {
// 这个参数是cookie的名称
SimpleCookie simpleCookie = new SimpleCookie("sid");
// setcookie的httponly属性如果设为true的话,会增加对xss防护的安全系数。它有以下特点:
// 只能通过http访问,javascript无法访问
// 防止xss读取cookie
simpleCookie.setHttpOnly(true);
simpleCookie.setPath("/");
// maxAge=-1表示浏览器关闭时失效此Cookie
simpleCookie.setMaxAge(-1);
return simpleCookie;
}
/**
* SessionDAO的作用是为Session提供CRUD并进行持久化的一个shiro组件 MemorySessionDAO 直接在内存中进行会话维护
* EnterpriseCacheSessionDAO
* 提供了缓存功能的会话维护,默认情况下使用MapCache实现,内部使用ConcurrentHashMap保存缓存的会话。
*
* @return
*/
@Bean
public SessionDAO sessionDAO(String redisHost, String redisPort, String password) {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager(redisHost, redisPort, password));
return redisSessionDAO;
}
@Bean
public SessionIdGenerator sessionIdGenerator() {
return new JavaUuidSessionIdGenerator();
}
// 配置自定义的权限登录器
@Bean
public CustomRealm customRealm() {
CustomRealm authRealm = new CustomRealm();
return authRealm;
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
*
* @return
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager manager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(manager);
return advisor;
}
}