花了一天时间,算是完成了shiro登录验证的这一基本功能。
https://www.w3cschool.cn/shiro/andc1if0.html 这个教程可以多看看,核心的基础功能很重要。
实现shiro
第一步,引入所需要的依赖
在pom.xml文件中加入
<properties>
<!-- log4j日志文件管理包版本 -->
<slf4j.version>1.7.7</slf4j.version>
<log4j.version>1.2.17</log4j.version>
</properties>
<!-- log start -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<!-- 格式化对象,方便输出日志 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.1.41</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- log end -->
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.3.2</version>
</dependency>
<!-- ehcache -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>
第二步:在web.xml中配置shiroFilter
<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>
第三步:新建类MyShiroRealm,代码里面有详细的注释
package com.wolwo.shiro;
public class MyShiroReaml extends AuthorizingRealm {
@Autowired
ShiroUserService shiroUserService;
/**
* Authorization(授权):访问控制的过程,即决定是否有权限去访问受保护的资源。
* @param pc
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
return null;
}
/**
* Authentication(身份验证):简称为“登录”,即证明用户是谁。
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 1. 把AuthenticationToken转换为UsernamePasswordToken
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
// 2. 从UsernamePasswordToken中获取email
String staffId = upToken.getUsername();
// 3. 若用户不存在,抛出UnknownAccountException异常
ShiroUser shiroUser = shiroUserService.selectByStaffId(staffId);
if (shiroUser == null) {
throw new UnknownAccountException("用户不存在!");
}
// 4.
// 根据用户的情况,来构建AuthenticationInfo对象并返回,通常使用的实现类为SimpleAuthenticationInfo
// 以下信息从数据库中获取
// (1)principal:认证的实体信息,可以是email,也可以是数据表对应的用户的实体类对象
Object principal = staffId;
// (2)credentials:密码
Object credentials = shiroUser.getPassword();
// (3)realmName:当前realm对象的name,调用父类的getName()方法即可
String realmName = getName();
// (4)盐值:取用户信息中唯一的字段来生成盐值,避免由于两个用户原始密码相同,加密后的密码也相同
ByteSource credentialsSalt = ByteSource.Util.bytes(staffId);
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt,
realmName);
return info;
}
}
第四步:在src目录下新建ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
<diskStore path="./target/tmp"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
<cache name="sampleCache1"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="true"
/>
<cache name="sampleCache2"
maxElementsInMemory="1000"
eternal="true"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
/>
</ehcache>
第五步:在spring配置文件spring-shiro.xml 中配置shiro,随后在spring配置文件里import引入
<?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 start -->
<!-- 1. 配置SecurityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="cacheManager" ref="cacheManager" />
<property name="authenticator" ref="authenticator"></property>
<!-- 可以配置多个Realm,其实会把realms属性赋值给ModularRealmAuthenticator的realms属性 -->
<property name="realms">
<list>
<ref bean="userRealm" />
</list>
</property>
</bean>
<!-- 2. 配置CacheManager -->
<!-- 2.1 需要加入ehcache的jar包及配置文件 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml" />
</bean>
<!-- 3. 配置Realm -->
<!-- 3.1 直接配置继承了org.apache.shiro.realm.AuthorizingRealm的bean -->
<bean id="userRealm" class="com.wolwo.shiro.MyShiroReaml">
<!-- 配置密码匹配器 -->
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!-- 加密算法为MD5 -->
<property name="hashAlgorithmName" value="MD5"></property>
<!-- 加密次数 -->
<property name="hashIterations" value="2"></property>
</bean>
</property>
</bean>
<!-- 4. 配置LifecycleBeanPostProcessor,可以自定义地来调用配置在Spring IOC容器中shiro bean的生命周期方法 -->
<!--<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />-->
<!-- 5. 使能够在IOC容器中使用shiro的注解,但必须在配置了LifecycleBeanPostProcessor之后才可以使用 -->
<!--<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>-->
<!-- 6. 配置ShiroFilter -->
<!-- 6.1 id必须和web.xml中配置的DelegatingFilterProxy的<filter-name>一致。 如果不一致,会抛出NoSuchBeanDefinitionException异常,因为shiro会在IOC容器中查找名称和<filter-name>
值一致的filter bean -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<!--<property name="loginUrl" value="/login.jsp" />-->
<!--<property name="successUrl" value="/WEB-INF/user/index.jsp" />-->
<!--<property name="unauthorizedUrl" value="/login.jsp" />-->
<!-- 配置哪些页面需要受保护,以及访问这些页面需要的权限 -->
<!--<property name="filterChainDefinitions">-->
<!--<value>-->
<!--<!– 第一次匹配优先的原则 –>-->
<!--/login.jsp = anon-->
<!--/user/login = anon-->
<!--/logout = logout-->
<!--/** = authc-->
<!--</value>-->
<!--</property>-->
</bean>
<!-- 7. 配置ModularRealmAuthenticator,可以实现多Realm认证 -->
<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<!-- 配置认证策略,只要有一个Realm认证成功即可,并且返回所有认证成功信息 -->
<property name="authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
</property>
</bean>
<!-- shiro end -->
</beans>
里面有很多被我注释起来了,是因为在测试的过程中,出现过很多错误,这些错误设计到权限、角色、还有shiro的一些其他功能。因为在学习中,尽量简化开发,而且目前只需要登录验证的功能,所以选择注释了起来。
但是有一个很严重的问题,我是用idea开发的,现在我项目的tomcat可以启动,但是启动后用redeplod或者restart server都会报错,只有关掉tomcat后,再启动才能运行,我怀疑是eccache的问题,但是还没有找到解决方案
第六步:测试(包括controller、service、mapper等)
service和mapper等在此省略
我新建了一个表专门用于shiro的用户登录
然后生成了service,mapper文件,写了一个 用staffId查询信息的方法。
在controller里的写法
package com.wolwo.shiro.controller;
import com.wolwo.system.domain.ShiroUser;
import com.wolwo.system.service.ShiroUserService;
import org.apache.log4j.Logger;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpSession;
import java.util.Date;
import java.util.Map;
@Controller
@RequestMapping("/user")
public class ShiroController {
@Autowired
ShiroUserService shiroUserService;
@RequestMapping(value = "register", method = RequestMethod.POST)
public void register(
// @RequestParam("staffId") String staffId, @RequestParam("password") String password
@RequestBody Map<String,String> map
) {
String staffId = map.get("staffId");
String password = map.get("password");
String passwordMd5 = new SimpleHash("md5", password, staffId, 2).toString();
ShiroUser shiroUser = new ShiroUser();
shiroUser.setStaffId(staffId);
shiroUser.setPassword(passwordMd5);
shiroUser.setCreateTime(new Date());
shiroUserService.insertSelective(shiroUser);
}
@RequestMapping(value = "login", method = RequestMethod.POST)
public void login(
// @RequestParam("staffId") String staffId, @RequestParam("password") String password
@RequestBody Map<String,String> map
) {
String staffId = map.get("staffId");
String password = map.get("password");
Subject subject = SecurityUtils.getSubject();
if (!subject.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken(staffId, password);
token.setRememberMe(false);
try {
subject.login(token);
System.out.println("登录成功");
} catch (IncorrectCredentialsException ice) {
System.out.println("邮箱/密码不匹配!");
} catch (LockedAccountException lae) {
System.out.println("账户已被冻结!");
} catch (AuthenticationException ae) {
System.out.println("token生成异常" + ae.getMessage());
}
}
}
}
有一点需要注意的是,controller中注册的接口加密方式,与shiro的密码加密方式保持一致才能使用,否则出现密码不正确、生成token失败等问题。
这些写完后,可以先使用注册和登录接口,测试是否可用,我是可用的。