什么是身份验证
身份验证就是登录的时候判断你的用户名和密码是否正确以及是否完全匹配。
shiro身份认证流程
上篇文章提到了shiro大致流程的三大核心概念,对于身份认证流程来说,同样也是那三大核心概念。
shiro身份认证流程就是shiro在应用程序中通过subject来进行认证和授权,而subject又委托给SecurityManager,而SecurityManager又需要从Realm中获取相对应的用户、角色、进行比较以确定用户身份是否合法。另外Realm包含我们对于用户验证的过程,所以是需要我们自己实现的。
代码实现
加入shiro相关jar包
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-quartz</artifactId>
<version>${shiro.version}</version>
</dependency>
在web.xml中添加shiro过滤器
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>ingcore-admin</display-name>
<!-- 首页 -->
<!--<welcome-file-list>-->
<!--<welcome-file>index.do</welcome-file>-->
<!--</welcome-file-list>-->
<welcome-file-list>
<welcome-file>index/main.do</welcome-file>
</welcome-file-list>
<!-- spring listener配置 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- spring配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- shiro Filter配置 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<!--<async-supported>true</async-supported>-->
<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>
<filter>
<filter-name>SessionTimeOutFilter</filter-name>
<filter-class>com.ingcore.core.filter.SesstionTimeoutFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>SessionTimeOutFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
对shiro进行配置
<?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">
<description>Shiro</description>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!--<bean id="shiroFilter" class="com.ingcore.core.shiro.ChainDefinitionSectionMetaSource">-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!--<property name="loginUrl" value="/rest/page/login"/>-->
<property name="loginUrl" value="/index/tologin.do"/>
<!--<property name="successUrl" value="/index/main.do"/>-->
<!--<property name="unauthorizedUrl" value="/rest/page/401"/>-->
<property name="unauthorizedUrl" value="/index/tologin.do"/>
<property name="filterChainDefinitions">
<value>
<!-- 静态资源允许访问 -->
/js/** = anon
/img/** = anon
/css/** = anon
/error.jsp = anon
<!-- 允许访问页面-->
<!--/index/main.do = anon-->
/index/authorizeLogin.do**= anon
/index/getCodeImg.do**= anon
/index/retrievePwd.do**= anon
/index/login.do**= anon
/index/mycinema.do**= anon
/index/flychannle.do**= anon
/index/logout.do**= anon
<!--<!– loginout –>-->
<!--/index/logout.do = logout-->
<!-- 其他资源需要认证 -->
/** = authc
<!--/**.do = authc-->
<!--/** = authc-->
</value>
</property>
</bean>
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--登录-->
<prop key="org.apache.shiro.authz.UnauthenticatedException">
redirect:/index/tologin.do
</prop>
<!--授权-->
<prop key="org.apache.shiro.authz.UnauthorizedException">
redirect:/index/tologin.do
</prop>
</props>
</property>
<property name="defaultErrorView" value="/error"/>
</bean>
<bean id="ehCacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>
<!-- 缓存管理器 使用Ehcache实现 -->
<bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<!-- <property name="cacheManager" ref="ehCacheManager"/> -->
<!--发布时使用-->
<property name="cacheManagerConfigFile" value="classpath:cache/ehcache-shiro.xml"/>
</bean>
<!-- 会话DAO -->
<!--<bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.MemorySessionDAO"/>-->
<bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO"/>
<!-- 会话管理器 -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!-- 定义的是全局的session会话超时时间,此操作会覆盖web.xml文件中的超时时间配置 3小时 -->
<property name="globalSessionTimeout" value="10800000"/>
<!-- 删除所有无效的Session对象,此时的session被保存在了内存里面 -->
<property name="deleteInvalidSessions" value="true"/>
<!-- 定义要使用的无效的Session定时调度器 -->
<property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
<!-- 需要让此session可以使用该定时调度器进行检测 -->
<property name="sessionValidationSchedulerEnabled" value="true"/>
<property name="sessionDAO" ref="sessionDAO"/>
<property name="sessionIdCookie" ref="cookieManager"/>
</bean>
<!-- 配置session的定时验证检测程序类,以让无效的session释放 -->
<bean id="sessionValidationScheduler"
class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">
<!-- 设置session的失效扫描间隔,单位为毫秒 5分钟 -->
<property name="sessionValidationInterval" value="60000"/>
<!-- 随后还需要定义有一个会话管理器的程序类的引用 -->
<property name="sessionManager" ref="sessionManager"/>
</bean>
<!--使用cookie支持chrome浏览器-->
<bean id="cookieManager" class="org.apache.shiro.web.servlet.SimpleCookie">
<property name="name" value="shirocookie"/>
<property name="path" value="/"/>
</bean>
<!--Realm实现-->
<bean id="myRealm" class="com.ingcore.core.shiro.MyRealm">
<property name="cachingEnabled" value="false"/>
</bean>
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realms">
<list>
<ref bean="myRealm"/>
</list>
</property>
<!-- cacheManager,集合spring缓存工厂 -->
<property name="cacheManager" ref="shiroEhcacheManager"/>
<property name="sessionManager" ref="sessionManager"/>
</bean>
<!-- Shiro生命周期处理器 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
</beans>
shiro提供的过滤器:
自定义realm
/**
* 认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
String userName = usernamePasswordToken.getUsername();
String password = String.valueOf(usernamePasswordToken.getPassword());
// 查询登陆用户
Map<String, Object> userParam = Maps.newHashMap();
userParam.put("loginName", userName);
Result<List<SysUser>> userResult = null;
try {
userResult = sysUserService.getUsersByCondition(userParam);
} catch (Exception e) {
logger.error("登录调用base接口异常", e);
throw new AuthenticationException("01.登录失败,请稍后重试!");
}
if(!ResultConstants.RESULT_CODE_SUCCESS.equals(userResult.getResultCode())) {
logger.error("MyRealm查询用户失败");
throw new RuntimeException(ResultConstants.RESULT_CDESC_FAILED);
}
List<SysUser> sysUserList = userResult.getResultData();
if(CollectionUtils.isEmpty(sysUserList) ||
sysUserList.get(0) == null) {
throw new UnknownAccountException("用户不存在");// 没找到帐号
} else {
SysUser user = sysUserList.get(0);
String pwd = user.getPassword();
// 还可以添加自己独有的判断,比如判断用户是否被禁用
boolean validError;
if(StringUtils.isBlank(pwd)) {
throw new IncorrectCredentialsException("密码错误");
}
try {
validError = !MD5Util.validPassword(password, pwd);
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
throw new IncorrectCredentialsException("密码错误");
}
if(validError) {
throw new IncorrectCredentialsException("密码错误");
}
return new SimpleAuthenticationInfo(
user, // 用户信息
password, // 密码
getName() // realm name
);
}
}
doGetAuthenticationInfo是当我们调用subject.login进行认证的方法 这个方法的参数token就是我们subject.login调用的
这里面我们就可以查询数据库对用户名和密码进行认证
如果认证成功将用户信息封装成SimpleAuthenticationInfo
认证失败根据几种情况抛出异常,
登录功能Controller方法
@RequestMapping("/login")
@ResponseBody
public Result<String> login(String username,
String passwd,
String verifyCode,
String flychannle,
String flag,
HttpSession session,
HttpServletResponse response,
HttpServletRequest request,
Model model) {
if (!ValidCodeUtil.validate(request, verifyCode)) {
return createFailedResult("验证码错误");
}
// 获取shiro中信息
//创建一个Subject实例,该实例认证需要使用上面创建的SecurityManager
Subject currentUser = SecurityUtils.getSubject();
//创建token令牌,账号和密码是前端传来的
UsernamePasswordToken token = new UsernamePasswordToken(username, passwd, username);
Boolean loginSuccess = false;
try {
currentUser.login(token);
loginSuccess = currentUser.isAuthenticated();
} catch (Exception e) {
log.error(username + ",登陆失败", e);
return createFailedResult(e.getMessage());
}
if(loginSuccess) {
SysUser sysUser = (SysUser)currentUser.getPrincipal();
if ("1".equals(flag)) {
Cookie cookie = new Cookie("cookie_user", username + "-" + passwd + "-" + flag);
cookie.setMaxAge(60 * 60 * 24 * 30); // cookie 保存30天
response.addCookie(cookie);
} else {
Cookie cookie = new Cookie("cookie_user", username + "-" + "" + "-" + flag);
cookie.setMaxAge(60 * 60 * 24 * 30); // cookie 保存30天
response.addCookie(cookie);
}
if ("1".equals(flychannle)) {
if(!redis.exists("fly_" + sysUser.getId() + "_" + IpUtil.getClientIpNoDot(request))) {
String flyCode = IdUtils.uuid2();
Cookie cookie = new Cookie("cookie_user_flychannle", flyCode);
redis.set("fly_" + flyCode + "_" + IpUtil.getClientIpNoDot(request), sysUser.getId());
redis.set("fly_" + sysUser.getId() + "_" + IpUtil.getClientIpNoDot(request), passwd);
cookie.setMaxAge(60 * 60 * 24); // 24小时免登陆
response.addCookie(cookie);
} else {
}
SecurityUtils.getSubject().getSession().setTimeout(24 * 60 * 60 * 1000);
} else {
Cookie cookie = new Cookie("cookie_user_flychannle", null);
response.addCookie(cookie);
SecurityUtils.getSubject().getSession().setTimeout(3 * 60 * 60 * 1000);
}
// 将cinema对象放入session中
String defaultCinema = sysUser.getCinemaCode();
String companyCode = sysUser.getCompanyCode();
Result<List<Cinema>> cinemaListResult;
if(sysUser.getCinemaChooseWay() == null) {
return createFailedResult("用户已过期");
}
if(PermisionConstants.USER_CINEMA_CHOOSE_WAY_ALL == sysUser.getCinemaChooseWay()) {
cinemaListResult = cinemaService.getCinemaListByCompanyCode(companyCode);
} else {
cinemaListResult = sysUserService.getCinemaByUserId(sysUser.getId());
}
if(!ResultConstants.RESULT_CODE_SUCCESS.equals(cinemaListResult.getResultCode())) {
// TODO 请求失败
return createFailedResult("查询用户授权影院失败");
}
List<Cinema> cinemaList = cinemaListResult.getResultData();
if(StringUtils.isNotBlank(defaultCinema)) {
for (Cinema cinema : cinemaList) {
if (defaultCinema.equals(cinema.getCinemaCode())) {
session.setAttribute(SESSION_KEY_DEFAULT_CINEMA, cinema);
}
}
}
session.setAttribute(SESSION_KEY_ALL_MY_CINEMA, cinemaList);
} else {
return createFailedResult("登录失败");
}
return createSuccessResult("index/main.do");
}
Subject实例认证需要使用上面创建的SecurityManager,SecurityManager在shiro配置文件中做了配置,并且将realm注入到了SecurityManager中。
Shiro初学中,更多功能尽在探索中。