您的位置:首页 > 编程语言 > Java开发

spring集成shiro实现权限认证和自动登录

2019-05-31 08:49 585 查看

在登录入口类中先创建用户名/密码身份验证Token。

UsernamePasswordToken token = new UsernamePasswordToken(phone, password);

然后调用subject.login(token);此时SecurityManager将会委托Authenticator进行身份验证,Authenticator会将token传入Realm从Realm获取身份验证信息,如果没有返回或抛出异常表示身份验证失败。用户登录成功可以将用户身份信息放入shiroSession中,在自定义的sessionExpiredFilter类中通过判断请求的session中是否存在用户信息,处理过期的登录请求。(浏览器每次请求会将sessionId通过request传给服务端,通过sessionId获取当前用户的session信息)

Subject subject = SecurityUtils.getSubject();
User user = userService.getUserByPhone(phone);
//创建用户名、密码身份验证Token
UsernamePasswordToken token = new UsernamePasswordToken(phone, password);
String loginToken = new Md5Hash(new Date().toString() + ShiroSession.getId()).toHex();

try {
subject.login(token);
ShiroSession.set("user_info", JSON.toJSONString(user));
//如果登录成功且设置了7天自动登录
if (remember == true) {
userService.updateToken(user.getPhone(), loginToken);
//设置客户端cookie保存7天
CookieUtil.addCookie(response, "loginToken", loginToken, 7);
}
return ResponseUtils.buildOKResponse(JSON.toJSONString(new User(user.getId(), user.getNickName())));

UserRealm用户权限认证:

UserRealm继承AuthorizingRealm类,实现两个方法doGetAuthenticationInfo和doGetAuthorizationInfo
在doGetAuthenticationInfo中从通过
String phone = (String) token.getPrincipal();获得角色信息
String password = new String((char[]) token.getCredentials());获得登录密码
然后与从数据库中查询出的用户密码进行匹配,进行判断逻辑处理,无误后将用户的登录信息存入SimpleAuthenticationInfo并返回:

SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(
phone,
password,
ByteSource.Util.bytes(phone),//设置username、pwd都没问题
getName() //realm name(唯一)
);
return info;

在doGetAuthorizationInfo类中主要实现对已认证的用户分配资源权限:
String phone = (String) principalCollection.getPrimaryPrincipal();获得认证用户,然后通过自定义的getResourcesByUserId方法获取用户资源列表,放入HashSet中,最后调用SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.setStringPermissions(new HashSet<>(permissions));将集合内容填充到认证中

ResourceCheckFilter访问路径的认证

ResourceCheckFilter继承AccessControlFilter实现两个方法isAccessAllowed和onAccessDenied,负责访问路径的认证判断:

@Override
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o)
throws Exception {
Subject subject = getSubject(servletRequest, servletResponse);
String url = getPathWithinApplication(servletRequest);
return subject.isPermitted(url);
}

isAccessAllowed方法通过获取request的请求路径,调用isPermitted方法,判断该资源是否可用,如果返回true,走到下一个过滤器。如果没有下一个过滤器的话,表示具有了访问某个资源的权限,如果返回 false,则会调用 onAccessDenied 方法,去实现相应的当过滤不通过的时候执行的操作。
onAccessDenied方法对没有访问权限的页面做处理:

@Override
@ResponseBody
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse)
throws Exception {
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpServletRequest request = (HttpServletRequest) servletRequest;
if (WebUtil.isAjax(request)) {
//返回403 FORBIDDEN状态码
response.setStatus(403);
}
//如果是普通请求,进行页面重定向
else {
response.sendRedirect(request.getContextPath()+"/403");
}
return false;
}

UrlPermissionResolver解析权限字符串

实现PermissionResolver接口,它负责解析权限字符串到Permission实例,RolePermissionResolver用于根据角色解析相应的权限集合。
WildcardPermission是shiro默认的权限字符串的表示方式,此处根据我们自己的url格式自定义UrlPermission类进行权限匹配。
UrlPermission 是一个实现了Permission接口的类,它的implies方法的实现决定了权限是否匹配。

public class UrlPermissionResolver implements PermissionResolver {
@Override
public Permission resolvePermission(String s) {
if (s.startsWith("/")) {
return new UrlPermission(s);
} else {
return new WildcardPermission(s);
}
}
}

class UrlPermission implements Permission {
private String url;

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}

public UrlPermission() {
}

public UrlPermission(String url) {
this.url = url;
}

@Override
public boolean implies(Permission permission) {
if (!(permission instanceof UrlPermission)) {
return false;
}
UrlPermission urlPermission = (UrlPermission) permission;
PatternMatcher patternMatcher = new AntPathMatcher();
return patternMatcher.matches(this.getUrl(), urlPermission.getUrl());
}
}

spring-shiro.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<!--自定义权限字符串解析规则,用/替换:-->
<bean id="urlPermissionResolver" class="com.legolas.blog.config.shiroconfig.UrlPermissionResolver"/>
<!--用户及权限Realm实现-->
<bean id="permissionRealm" class="com.legolas.blog.config.shiroconfig.UserRealm"></bean>

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="permissionRealm"/>
<property name="sessionManager" ref="sessionManager"/>
<property name="cacheManager" ref="ehCacheManager"/>
<property name="authorizer.permissionResolver" ref="urlPermissionResolver"/>
</bean>

<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!-- 设置超时时间 -->
<property name="globalSessionTimeout" value="6048000"/>
<property name="deleteInvalidSessions" value="true"/>
<property name="sessionValidationSchedulerEnabled" value="true"/>
<property name="sessionIdCookieEnabled" value="true"/>
<property name="sessionIdCookie" ref="sessionIdCookie"/>
</bean>

<!--shiroFilter-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="loginUrl" value="/index"/>
<property name="securityManager" ref="securityManager"/>
<property name="successUrl" value="/index"/>
<property name="filterChainDefinitions">
<!--默认拦截器:
authc:基于表单的拦截器,没有登陆会跳到相应的登录页面登录如 /**=authc
logout:退出拦截器如/logout=logout
anon:匿名拦截器,即不需要登录就可访问 如/static/**=anon
authc是认证过,user是登录过,如果开启了rememberMe功能的话,后者(user)也是可以通过的,
而前者(authc)通过不了。故我们用authc来校验一些关键操作,比如购买,我们可以采用user校验即可。
而支付的时候,我们需要认证的用户,那就需要authc了。
-->
<value>
/assets/** = anon

/admin/**= sessionExpiredFilter,user,resourceFilter
<!--/archive = sessionExpiredFilter   归档中需区分登录超时以显示锁定文章///该设计不合理,还是将锁定与未锁定文章放两个入口-->
<!-- /admin/**= authc,resourceFilter-->
</value>
</property>
</bean>

<!--角色过滤器-->
<bean id="resourceFilter" class="com.legolas.blog.config.shiroconfig.ResourceCheckFilter"/>
<bean id="sessionExpiredFilter" class="com.legolas.blog.config.shiroconfig.SessionExpiredFilter"/>

<!--info的加密算法-->
<bean id="hashMatch" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="md5"/>
</bean>

<!--缓存管理-->
<bean id="ehCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache/shiro-ehcache.xml"/>
</bean>

</beans>

可以看到上面spring-shiro的shiroFilter拦截器中还定义了一个sessionExpiredFilter,在该过滤器中我们实现自己的自动登录逻辑和登录过期请求的处理:

首先在用户登录认证代码中如果用户登录成功,且选择了自动登录选项,我们保存用户的登录状态信息到session中,然后生成一个loginToken认证码,更新到user表的字段中:

String loginToken = new Md5Hash(new Date().toString() + ShiroSession.getId()).toHex();
try {
subject.login(token);
ShiroSession.set("user_info", JSON.toJSONString(user));
//如果登录成功且设置了7天自动登录
if (remember == true) {
userService.updateToken(user.getPhone(), loginToken);
//设置客户端cookie保存7天
CookieUtil.addCookie(response, "loginToken", loginToken, 7);
}
return ResponseUtils.buildOKResponse(JSON.toJSONString(new User(user.getId(), user.getNickName())));

}catch(){

}

SessionExpiredFilter过滤器

在自定义的SessionExpiredFilter过滤器中写http请求的前置拦截逻辑代码,先判断session中用户的登录状态,若不存在则用户未登录,然后判断request请求中是否存在loginToken(未选择自动登录或者已过7天token失效),若存在,通过该token值从数据库中查询与之匹配的用户,若存在,则执行登录代码,若不存在,则执行逻辑跳转处理:

public class SessionExpiredFilter extends PathMatchingFilter {
@Autowired
UserService userService;

@Override
protected boolean onPreHandle(ServletRequest servletRequest, ServletResponse servletResponse, Object mappedValue) throws Exception {
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpServletRequest request = (HttpServletRequest) servletRequest;
if (ShiroSession.get("user_info") != null) {
return true;
} else {
//判断请求端带过来cookie是否存在
String loginToken = CookieUtil.findCookieByName(request, "loginToken");
if (StringUtils.isNotBlank(loginToken)) {
//到数据库查询有没有该Cookie
User user = userService.findUserByToken(loginToken);
if (user != null) {
try {
//重新登录
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(user.getPhone(), user.getPassword());
subject.login(token);
ShiroSession.set("user_info", JSON.toJSONString(user));
return true;
} catch (AuthenticationException e) {
e.printStackTrace();
return false;
}
} else {
//没有该Cookie与之对应的用户(Cookie不匹配)
CookieUtil.clearCookie(request, response, "loginToken");
return false;
}
} else {
//没有登录,也没有cookie凭证
if (WebUtil.isAjax(request)) {
response.setStatus(401);
} else {
response.sendRedirect(request.getContextPath() + "/401");
}
return false;
}
}
}
}

在此过程中尝试过使用UserInterceptor,在拦截器中处理登录过期请求,但是shiroFilter过滤器的处理顺序在Spring的拦截器之前执行,处理请求无法到达interceptor。

配置中的注入的shiroFilter在web.xml文件中通过DelegatingFilterProxy对servlet filter的代理,来实现自定义的filter使配置的shiro拦截器生效。

web.xml配置文件

<!--若集成redis管理session时配置该filter放在最前面,使其具有最优先地位接管所有请求的session-->
<!--
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
-->

<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: