shiro是一个安全框架,用于认证和授权,我觉得与springsecurity相比它上手更容易,同时如果是简单的登录拦截也可以用登录拦截器实现,下面先进行springboot整合 shiro的过程
1.shiro依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.7.1</version>
</dependency>
2.shiro配置
配置安全管理器:SecurityManager
Authenticator 的职责是验证用户帐号,是ShiroAPI 中身份验证核心的入口点:如果验证成功,将返回AuthenticationInfo验证信息;此信息中包含了身份及凭证;如果验证失败将抛出相应的AuthenticationException异常
•SecurityManager接口继承了Authenticator,另外还有一个ModularRealmAuthenticator实现,其委托给多个Realm 进行验证,验证规则通过AuthenticationStrategy接口指定
/**
* 安全管理器
*/
@Bean
public SecurityManager securityManager(UserRealm userRealm)
{
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(userRealm);
// 记住我
securityManager.setRememberMeManager(rememberMe ? rememberMeManager() : null);
// 注入缓存管理器;
securityManager.setCacheManager(getEhCacheManager());
// session管理器
securityManager.setSessionManager(sessionManager());
return securityManager;
}
哪些url是需要拦截的,哪些是不需要拦截的,登录页面、登录成功页面的url、自定义的Realm等这些信息需要设置到Shiro中
shiro过滤器配置:
/**
* Shiro过滤器配置
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager)
{
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// Shiro的核心安全接口,这个属性是必须的
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 身份认证失败,则跳转到登录页面的配置
shiroFilterFactoryBean.setLoginUrl(loginUrl);
// 权限认证失败,则跳转到指定页面
shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);
// Shiro连接约束配置,即过滤链的定义
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 对静态资源设置匿名访问
filterChainDefinitionMap.put("/favicon.ico**", "anon");
//加入自己的路径和访问权限
// 系统权限列表
// filterChainDefinitionMap.putAll(SpringUtils.getBean(IMenuService.class).selectPermsAll());
Map<String, Filter> filters = new LinkedHashMap<String, Filter>();
filters.put("onlineSession", onlineSessionFilter());
filters.put("syncOnlineSession", syncOnlineSessionFilter());
filters.put("captchaValidate", captchaValidateFilter());
filters.put("kickout", kickoutSessionFilter());
// 注销成功,则跳转到指定页面
filters.put("logout", logoutFilter());
shiroFilterFactoryBean.setFilters(filters);
// 所有请求需要认证
filterChainDefinitionMap.put("/**", "user,kickout,onlineSession,syncOnlineSession");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
安全管理器关联自己的Realm
@Bean(name="security")
public DefaultWebSecurityManager getDefaultManager(@Qualifier("realm")UserRealm userRealm){
DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
//关联自己的realm
defaultSecurityManager.setRealm(userRealm);
return defaultSecurityManager;
}
@Bean(name = "realm")
//创建realm对象
public UserRealm userRealm(){
return new UserRealm();
}
关联自定义的其他管理器
@Bean
public SecurityManager securityManager(UserRealm userRealm)
{
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(userRealm);
// 记住我
securityManager.setRememberMeManager(rememberMe ? rememberMeManager() : null);
// 注入缓存管理器;
securityManager.setCacheManager(getEhCacheManager());
// session管理器
securityManager.setSessionManager(sessionManager());
return securityManager;
}
/**
* 缓存管理器 使用Ehcache实现
*/
@Bean
public EhCacheManager getEhCacheManager()
{
net.sf.ehcache.CacheManager cacheManager = net.sf.ehcache.CacheManager.getCacheManager("ruoyi");
EhCacheManager em = new EhCacheManager();
if (StringUtils.isNull(cacheManager))
{
em.setCacheManager(new net.sf.ehcache.CacheManager(getCacheManagerConfigFileInputStream()));
return em;
}
else
{
em.setCacheManager(cacheManager);
return em;
}
}
缓存:Shiro内部相应的组件(DefaultSecurityManager)会自动检测相应的对象(如Realm)是否实现了CacheManagerAware并自动注入相应的CacheManager。
Shiro提供了CachingRealm,其实现了CacheManagerAware接口,提供了缓存的一些基础实现;
•AuthenticatingRealm及AuthorizingRealm也分别提供了对AuthenticationInfo和AuthorizationInfo信息的缓存。
Session 缓存
•如SecurityManager实现了SessionSecurityManager,其会判断SessionManager是否实现了CacheManagerAware接口,如果实现了会把CacheManager设置给它。
•SessionManager也会判断相应的SessionDAO(如继承自CachingSessionDAO)是否实现了CacheManagerAware,如果实现了会把CacheManager设置给它
•设置了缓存的SessionManager,查询时会先查缓存,如果找不到才查数据库。
自定义会话工厂:
//自定义sessionFactory会话
@Component
public class OnlineSessionFactory implements SessionFactory
{
@Override
public Session createSession(SessionContext initData)
{
OnlineSession session = new OnlineSession();
if (initData != null && initData instanceof WebSessionContext)
{
WebSessionContext sessionContext = (WebSessionContext) initData;
HttpServletRequest request = (HttpServletRequest) sessionContext.getServletRequest();
if (request != null)
{
UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
// 获取客户端操作系统
String os = userAgent.getOperatingSystem().getName();
// 获取客户端浏览器
String browser = userAgent.getBrowser().getName();
session.setHost(IpUtils.getIpAddr(request));
session.setBrowser(browser);
session.setOs(os);
}
}
return session;
}
}
3.登陆时记录用户信息
在控制器层:
@ApiOperation(value="登录")
@PostMapping("/login")
public ResponseResult<User> toLogin(@ApiParam(name="用户对象",value="传入json格式",required=true)LoginForm loginForm)
{
User user = userService.selectUserByLoginName(loginForm.getUserName(),loginForm.getPassword(), false);
if (user != null) {
UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(),user.getPassword(), false);
Subject subject = SecurityUtils.getSubject();
subject.login(token);
return ResponseResult.success();}
else return ResponseResult.error();
}
4.shiro一些工具类的学习
SecurityUtils.getSubject()是每个请求创建一个Subject, 并保存到ThreadContext的resources(ThreadLocal<Map<Object, Object>>)变量中,也就是一个http请求一个subject,并绑定到当前线程。
subject.login()登陆认证成功后,下一次请求如何知道是那个用户的请求呢?
内部原理:1个请求1个Subject原理:由于ShiroFilterFactoryBean本质是个AbstractShiroFilter过滤器,所以每次请求都会执行doFilterInternal里面的createSubject方法。
源码:
public interface Subject {
Object getPrincipal();
PrincipalCollection getPrincipals();
boolean isPermitted(String var1);
boolean isPermitted(Permission var1);
...省略其他方法}
public interface SecurityManager extends Authenticator, Authorizer, SessionManager {
Subject login(Subject var1, AuthenticationToken var2) throws AuthenticationException;
void logout(Subject var1);
Subject createSubject(SubjectContext var1);
}
public abstract class SecurityUtils {
private static volatile SecurityManager securityManager;
public SecurityUtils() {
}
public static Subject getSubject() {
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}
public static void setSecurityManager(SecurityManager securityManager) {
SecurityUtils.securityManager = securityManager;
}
public static SecurityManager getSecurityManager() throws UnavailableSecurityManagerException {
SecurityManager securityManager = ThreadContext.getSecurityManager();
if (securityManager == null) {
securityManager = SecurityUtils.securityManager;
}
if (securityManager == null) {
String msg = "No SecurityManager accessible to the calling code, either bound to the " + ThreadContext.class.getName() + " or as a vm static singleton. This is an invalid application configuration.";
throw new UnavailableSecurityManagerException(msg);
} else {
return securityManager;
}
}
}
shiro内置的session
session.setAttribute(“username”,username)就是将username保存到session中,session的key值为username,其信息(value)为username,或者引用值。这样以后可以通过session.getAttribute(“username”)的方法来获取这个对象。通常,当用户已经登录系统后,就可以在session中存储一个用户信息对象,伺候可以随时从session中将这个对象取出来进行一些操作,比如身份验证等等。
request.getSession()可以获得HttpSession类型的对象,通常称之为session对象,session对象的作用域为一次会话,通常浏览器不关闭,保存的值就不会消失,当然也会出现session超时。服务器里面可以设置session的超时时间,web.xml中有一个session time out的地方,tomcat默认为30分钟。
session. setAttribute(“key”,value)是session设置值的方法,原理同Map集合。
getAttribute的返回值类型是Object,需要向下转型,转成你的userName类型的。比如,String session1= (String)session.getAttribute(“student”) ;
5.自定义登录拦截器
这种办法不需要引入依赖,只需要继承HandlerInterceptor 即可 实现非常简单:
@Slf4j
public class UserLoginInterceptor implements HandlerInterceptor {
/**
* true 表示继续流程,false表示中断
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("preHandle...");
User user = (User) request.getSession().getAttribute(MallConst.CURRENT_USER);
if (user == null) {
log.info("user=null");
throw new UserLoginException();
}
return true;
}
}
然后定义一个配置类,启动时springboot便能进行自动配置
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new UserLoginInterceptor())
.addPathPatterns("/**")//自己添加需要拦截的路径
.excludePathPatterns("/error", "/user/login", "/user/register", "/categories", "/products/*");//哪些路径不需要拦截
}
}