文章目录
先总结一下使用shiro的原因
shiro的特点及功能
- 将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。
- 可以运行在web应用,非web应用,集群分布式应用中越来越多的用户开始使用shiro
- spring security依赖spring运行,而shiro就相对独立
- 它可以实现如下的功能:
4.1. 验证用户
4.2.对用户执行访问控制,如:判断用户是否具有角色admin,判断用户是否拥有访问的资源权限。
4.3.在任何环境下使用SessionAPI。例如C/S程序
4.4.可以使用多个用户数据源。例如一个是Oracle数据库,另外一个是MySQL数据库。
4.5.单点登录(SSO)功能
4.6."Remember Me"服务,类似于购物车的功能,shiro官方建议开启。
shiro组成简介
shiro的4大组成部分——身份认证,授权,会话管理和加密
Authentication:身份验证(身份认证),简称"登录"。
Authorization:授权,给用户给用户分配角色或者权限资源。
Session Manager:用户Session管理器,可以让C/S程序也使用Session来控制权限。
Cryptography:将JDK中复杂的密码加密方式进行封装。
其它相关内容
shiro在代码中的使用
pom.xml
shiro在maven中使用的包
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
web.xml
添加shiro过滤器
<!-- shiro过滤器 start -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<!-- 设置true由servlet容器控制filter的生命周期 -->
<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>
<!-- shiro 过滤器 end -->
spring-shiro.xml
shiro配置文件(和spring结合)
<?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">
<!--开启shiro的注解 -->
<bean id="advisorAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
<property name="proxyTargetClass" value="true"></property>
</bean>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor" />
<!--注入自定义的Realm -->
<bean id="customRealm" class="com.ufgov.util.shiro.CustomRealm"></bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="customRealm"></property>
</bean>
<!--配置ShiroFilter -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"></property>
<!--登入页面 -->
<property name="loginUrl" value="/login_achieve/login"></property>
<!--登入成功页面 -->
<property name="successUrl" value="/home" />
<property name="filters">
<map>
<!--退出过滤器 -->
<entry key="logout" value-ref="logoutFilter" />
</map>
</property>
<!--URL的拦截 -->
<property name="filterChainDefinitions">
<value>
/share = authc
/login_achieve/logout = logout
</value>
</property>
</bean>
<!--自定义退出LogoutFilter -->
<bean id="logoutFilter" class="com.ufgov.util.shiro.SystemLogoutFilter">
<property name="redirectUrl" value="/login_achieve/login" />
</bean>
</beans>
引入shiro的配置文件
可以在web.xml,也可以在spring的配置文件中。我配置在spring的配置文件applicationContext.xml中
<!-- 引入shiro-->
<import resource="spring-shiro.xml"></import>
在spring mvc的中配置内容
spring-mvc.xml中需要配置一些跳转,但是我还没有测试到
<!-- shiro相关配置 -->
<!-- 未认证或未授权时跳转必须在springmvc里面配,spring-shiro里的shirofilter配不生效 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--表示捕获的异常 -->
<prop key="org.apache.shiro.authz.UnauthorizedException">
<!--捕获该异常时跳转的路径 -->
/403
</prop>
<!--表示捕获的异常 -->
<prop key="org.apache.shiro.authz.UnauthenticatedException">
<!--捕获该异常时跳转的路径 -->
/403
</prop>
</props>
</property>
</bean>
CustomRealm
CustomRealm
我的理解主要是重写shiro的认证和授权
package com.ufgov.util.shiro;
import java.util.ArrayList;
import java.util.List;
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.authc.UsernamePasswordToken;
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 org.springframework.stereotype.Component;
/**
* Realm:域,
* Shiro从Realm获取安全数据(如用户、角色、权限),
* 就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;
* 也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource , 即安全数据源。
* @author Administrator
*
*/
@Component
public class CustomRealm extends AuthorizingRealm {
/**
* 授权
* 该方法尚未启用,因为之前没有使用shiro,所以部分内容还需要修改
* @param principalCollection 这个可以理解为当事人的信息!
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String userName = (String) principalCollection.getPrimaryPrincipal();
List<String> permissionList = new ArrayList<String>();
permissionList.add("user:add");
permissionList.add("user:delete");
if (userName.equals("zhou")) {
permissionList.add("user:query");
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(permissionList);
info.addRole("admin");
return info;
}
/**
* 认证。这次身份认证会委托给Security Manager,
* 而Security Manager又会委托给Authenticator,
* 接着Authenticator会把传过来的token再交给我们自己注入的Realm进行数据匹配从而完成整个认证
* 最直白的是subject.login(token)会来调用这个方法
*
* @param authenticationToken
* @return null表示没有找到认证信息,换句话说,也就是根据账号没有找到用户信息
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
String userAccount = (String) token.getUsername();
char[] password = token.getPassword();
return new SimpleAuthenticationInfo(
userAccount, // 用户名
password, // 密码
getName() // realm name
);
}
}
SystemLogoutFilter
继承shiro的LogoutFilter
用来在注销时释放资源
package com.ufgov.util.shiro;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.session.SessionException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.LogoutFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ufgov.entity.SysLoginLog;
import com.ufgov.entity.SysUserInfo;
import com.ufgov.service.SysLoginLogService;
import com.ufgov.service.SysOnceLoginLogService;
import com.ufgov.util.Constans;
import com.ufgov.util.CookieUtil;
import com.ufgov.util.DateUtils;
import com.ufgov.util.MemcachedUtils;
import com.ufgov.util.ToolClass;
@Service
public class SystemLogoutFilter extends LogoutFilter {
@Autowired
private SysLoginLogService sysLoginLogServiceImpl;
@Autowired
private SysOnceLoginLogService sysOnceloginLogServiceImpl;
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
//在这里执行退出系统前需要清空的数据
Subject subject = getSubject(request, response);
//这个对应配置文件中配置logoutFilter的redirectUrl
String redirectUrl = getRedirectUrl(request, response, subject);
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse rep = (HttpServletResponse) response;
SysUserInfo sysUserInfo = MemcachedUtils.getUserInfo(req);
String loginLogToken = Constans.LOGIN_LOG_TOKEN + sysUserInfo.getAccount();
Object logId = MemcachedUtils.get(loginLogToken);
if(logId != null){
SysLoginLog sysLoginLog = new SysLoginLog();
sysLoginLog.setLogId(logId.toString());
sysLoginLog.setLogoutTime(DateUtils.getTime());
sysLoginLogServiceImpl.updateByPrimaryKeySelective(sysLoginLog);
}
MemcachedUtils.clearUserInfo(req);
String tokenId = MemcachedUtils.getTokenId(req);
sysOnceloginLogServiceImpl.updatelastOut(ToolClass.getIp(req));// 更新最后登出时间
MemcachedUtils.delete(Constans.TOKEN_ID_ROLE_URL + tokenId);
MemcachedUtils.delete(loginLogToken);
CookieUtil.removeCookie(rep,tokenId);
try {
subject.logout();
} catch (SessionException ise) {
ise.printStackTrace();
}
issueRedirect(request, response, redirectUrl);
//返回false表示不执行后续的过滤器,直接返回跳转到登录页面
return false;
}
}
LoginController
摘抄一些登录的相关方法
/**
* Created by Qk on 2017/2/28.
*/
@Controller
@RequestMapping(value = "/login_achieve")
public class LoginController {
......
/**
* @Author Qk
* @Date 2017/3/1 9:43
* @Description 用户登录
* @Param
* @Return
*/
@RequestMapping(method = RequestMethod.POST, value = "/info")
@ResponseBody
public ResponseState login(HttpServletRequest request, HttpServletResponse response) throws Exception {
ResponseState state = new ResponseState();
//2018/1/31 lihhz 如果超时,删除user/cookie等
long noOptTimeOut = Long.parseLong(Constans.NO_OPT_TIME);
String key = Constans.NO_OPT_TIME_STR + CookieUtil.getUid(request, Constans.TOKEN_ID);
long lastOptTime = MemcachedUtils.get(key) != null ? (Long) MemcachedUtils.get(key) : 0;
long thisOptTime = (new Date()).getTime();
if (thisOptTime - lastOptTime > noOptTimeOut) {
//删除这个key,因为如果Memcached的超时时间远大于无操作设置时间,会出错
MemcachedUtils.delete(key);
String userKey = Constans.USER_INFO + CookieUtil.getUid(request, Constans.TOKEN_ID);
// CookieUtil.removeCookie(response,Constans.TOKEN_ID);
MemcachedUtils.delete(userKey);
} else {
SysUserInfo userInfo = MemcachedUtils.getUserInfo(request);
if (userInfo != null) {
//已经登录调到控制台
state.setData(Constans.LOGIN_SUCCESS_URL);
return state;
}
}
String userAccount = request.getParameter("userAccount");
String password = request.getParameter("password");
String code = request.getParameter("code");
// 账号为空,密码为空
if (StringUtils.isEmpty(userAccount) || StringUtils.isEmpty(password)) {
state.setSuccess(false);
state.setMessage("用户名或密码错误");
return state;
}
// 验证码是否正确
String picId = CookieUtil.getUid(request, "PICID");// cookie中获取验证码id
// 验证码或者cookie中的验证码id为空
if (StringUtils.isBlank(picId) || StringUtils.isBlank(code)) {
MemcachedUtils.deleteAfterGet(picId);
state.setSuccess(false);
state.setMessage("没有获取到验证码!");
return state;
}
String picCode_true = (String) MemcachedUtils.deleteAfterGet(picId);
// 验证码失效
if (StringUtils.isBlank(picCode_true)) {
state.setSuccess(false);
state.setMessage("验证码失效!");
return state;
}
if (!picCode_true.toLowerCase().equals(code.toLowerCase())) {
state.setSuccess(false);
state.setMessage("验证码输入有误或超时过期");
return state;
}
// 登陆次数超过5次及正确性校验
state = loginService.loginValidate(userAccount, password);
if (!state.getSuccess()) {
return state;
}
SysUserInfo sysUserInfo = loginService.getSysUserInfo(userAccount);
//如果是CA用户,且必须同统一门户登录验证
if ("yes".equals(loginoss) && sysUserInfo != null && "1".equals(sysUserInfo.getCaAccount())) {
state.setSuccess(false);
state.setMessage("请您从统一门户登陆");
return state;
}
doLogin(request, response, CookieUtil.getUid(request, Constans.TOKEN_ID), sysUserInfo);
SecurityUtils.getSubject().login(new UsernamePasswordToken(userAccount, password));
state.setData(Constans.LOGIN_SUCCESS_URL);
return state;
}
......
}
总结
事实上,这里只用到了shiro的登录和注销,没有涉及到权限方面的内容。后续再补充。