注:该 shiro 教程来源于 B 站上的一个教程,由于源码是付费的,我就不分享了,下篇讲解 springboot 搭配 shiro 进行使用。
我的个人博客:
天涯志
我的公众号:菜鸟小谢
6. 整合 SpringBoot 项目实战
6.0 整合思路
6.1 创建 springboot 项目
6.2 引入 shiro 依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.5.3</version>
</dependency>
6.3 配置 shiro 环境
0. 创建配置类
1. 配置 shiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager){
// 创建 shiro 的 filter
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 注入安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
return shiroFilterFactoryBean;
}
2. 配置 WebSecurityManager
@Bean
public DefaultWebSecurityManager getSecurityManager(Realm realm){DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
3. 创建自定义 realm
public class CustomerRealm extends AuthorizingRealm {
// 处理授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {return null;}
// 处理认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws
AuthenticationException {return null;}
}
4. 配置自定义 realm
// 创建自定义 realm
@Bean
public Realm getRealm(){return new CustomerRealm();
}
5. 编写控制器跳转至 index.html
@Controller
public class IndexController {@RequestMapping("index")
public String index(){System.out.println("跳转至主页");
return "index";
}
}
6. 启动 springboot 应用访问 index
注意:
- 默认在配置好 shiro 环境后默认环境中没有对项目中任何资源进行权限控制, 所有现在项目中所有资源都可以通过路径访问
-
7. 加入权限控制
-
修改 ShiroFilterFactoryBean 配置
// 注入安全管理器 shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String,String> map = new LinkedHashMap<>(); map.put("/**","authc"); // 配置认证和授权规则 shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
8. 重启项目访问查看
6.4 常见过滤器
- 注意: shiro 提供和多个默认的过滤器,我们可以用这些过滤器来配置控制指定 url 的权限:
配置缩写 | 对应的过滤器 | 功能 |
---|---|---|
anon | AnonymousFilter | 指定 url 可以匿名访问 |
authc | FormAuthenticationFilter | 指定 url 需要 form 表单登录,默认会从请求中获取 username 、password ,rememberMe 等参数并尝试登录,如果登录不了就会跳转到 loginUrl 配置的路径。我们也可以用这个过滤器做默认的登录逻辑,但是一般都是我们自己在控制器写登录逻辑的,自己写的话出错返回的信息都可以定制嘛。 |
authcBasic | BasicHttpAuthenticationFilter | 指定 url 需要 basic 登录 |
logout | LogoutFilter | 登出过滤器,配置指定 url 就可以实现退出功能,非常方便 |
noSessionCreation | NoSessionCreationFilter | 禁止创建会话 |
perms | PermissionsAuthorizationFilter | 需要指定权限才能访问 |
port | PortFilter | 需要指定端口才能访问 |
rest | HttpMethodPermissionFilter | 将 http 请求方法转化成相应的动词来构造一个权限字符串,这个感觉意义不大,有兴趣自己看源码的注释 |
roles | RolesAuthorizationFilter | 需要指定角色才能访问 |
ssl | SslFilter | 需要 https 请求才能访问 |
user | UserFilter | 需要已登录或“记住我”的用户才能访问 |
6.5 认证实现
1. 在 login.jsp 中开发认证界面
<form action="${pageContext.request.contextPath}/user/login" method="post">
用户名:<input type="text" name="username" > <br/>
密码 : <input type="text" name="password"> <br>
<input type="submit" value="登录">
</form>
2. 开发 controller
@Controller
@RequestMapping("user")
public class UserController {
/**
* 用来处理身份认证
* @param username
* @param password
* @return
*/
@RequestMapping("login")
public String login(String username,String password){
// 获取主体对象
Subject subject = SecurityUtils.getSubject();
try {subject.login(new UsernamePasswordToken(username,password));
return "redirect:/index.jsp";
} catch (UnknownAccountException e) {e.printStackTrace();
System.out.println("用户名错误!");
}catch (IncorrectCredentialsException e){e.printStackTrace();
System.out.println("密码错误!");
}
return "redirect:/login.jsp";
}
}
- 在认证过程中使用 subject.login 进行认证
3. 开发 realm 中返回静态数据(未连接数据库)
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {System.out.println("==========================");
String principal = (String) token.getPrincipal();
if("xiaochen".equals(principal)){return new SimpleAuthenticationInfo(principal,"123",this.getName());
}
return null;
}
}
4. 启动项目以 realm 中定义静态数据进行认证
- 认证功能没有 md5 和随机盐的认证就实现啦
6.6 退出认证
1. 开发页面退出连接
2. 开发 controller
@Controller
@RequestMapping("user")
public class UserController {
/**
* 退出登录
*
*/
@RequestMapping("logout")
public String logout(){Subject subject = SecurityUtils.getSubject();
subject.logout();// 退出用户
return "redirect:/login.jsp";
}
}
3. 修改退出连接访问退出路径
4. 退出之后访问受限资源立即返回认证界面
6.7 MD5、Salt 的认证实现
1. 开发数据库注册
0. 开发注册界面
<h1> 用户注册 </h1>
<form action="${pageContext.request.contextPath}/user/register" method="post">
用户名:<input type="text" name="username" > <br/>
密码 : <input type="text" name="password"> <br>
<input type="submit" value="立即注册">
</form>
1. 创建数据表结构
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (`id` int(6) NOT NULL AUTO_INCREMENT,
`username` varchar(40) DEFAULT NULL,
`password` varchar(40) DEFAULT NULL,
`salt` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
SET FOREIGN_KEY_CHECKS = 1;
2. 项目引入依赖
<!--mybatis 相关依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.19</version>
</dependency>
3. 配置 application.properties 配置文件
server.port=8888
server.servlet.context-path=/shiro
spring.application.name=shiro
spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp
#新增配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shiro?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root
mybatis.type-aliases-package=com.baizhi.springboot_jsp_shiro.entity
mybatis.mapper-locations=classpath:com/baizhi/mapper/*.xml
4. 创建 entity
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String id;
private String username;
private String password;
private String salt;
}
5. 创建 DAO 接口
@Mapper
public interface UserDAO {void save(User user);
}
6. 开发 mapper 配置文件
<insert id="save" parameterType="User" useGeneratedKeys="true" keyProperty="id">
insert into t_user values(#{id},#{username},#{password},#{salt})
</insert>
7. 开发 service 接口
public interface UserService {
// 注册用户方法
void register(User user);
}
8. 创建 salt 工具类
public class SaltUtils {
/**
* 生成 salt 的静态方法
* @param n
* @return
*/
public static String getSalt(int n){char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890!@#$%^&*()".toCharArray();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++) {char aChar = chars[new Random().nextInt(chars.length)];
sb.append(aChar);
}
return sb.toString();}
}
9. 开发 service 实现类
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDAO userDAO;
@Override
public void register(User user) {
// 处理业务调用 dao
//1. 生成随机盐
String salt = SaltUtils.getSalt(8);
//2. 将随机盐保存到数据
user.setSalt(salt);
//3. 明文密码进行 md5 + salt + hash 散列
Md5Hash md5Hash = new Md5Hash(user.getPassword(),salt,1024);
user.setPassword(md5Hash.toHex());
userDAO.save(user);
}
}
10. 开发 Controller
@Controller
@RequestMapping("user")
public class UserController {
@Autowired
private UserService userService;
/**
* 用户注册
*/
@RequestMapping("register")
public String register(User user) {
try {userService.register(user);
return "redirect:/login.jsp";
}catch (Exception e){e.printStackTrace();
return "redirect:/register.jsp";
}
}
}
11. 启动项目进行注册
2. 开发数据库认证
0. 开发 DAO
@Mapper
public interface UserDAO {void save(User user);
// 根据身份信息认证的方法
User findByUserName(String username);
}
1. 开发 mapper 配置文件
<select id="findByUserName" parameterType="String" resultType="User">
select id,username,password,salt from t_user
where username = #{username}
</select>
2. 开发 Service 接口
public interface UserService {
// 注册用户方法
void register(User user);
// 根据用户名查询业务的方法
User findByUserName(String username);
}
3. 开发 Service 实现类
@Service("userService")
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDAO userDAO;
@Override
public User findByUserName(String username) {return userDAO.findByUserName(username);
}
}
4. 开发在工厂中获取 bean 对象的工具类
@Component
public class ApplicationContextUtils implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.context = applicationContext;}
// 根据 bean 名字获取工厂中指定 bean 对象
public static Object getBean(String beanName){return context.getBean(beanName);
}
}
5. 修改自定义 realm
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {System.out.println("==========================");
// 根据身份信息
String principal = (String) token.getPrincipal();
// 在工厂中获取 service 对象
UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
// 根据身份信息查询
User user = userService.findByUserName(principal);
if(!ObjectUtils.isEmpty(user)){
// 返回数据库信息
return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),
ByteSource.Util.bytes(user.getSalt()),this.getName());
}
return null;
}
6. 修改 ShiroConfig 中 realm 使用凭证匹配器以及 hash 散列
@Bean
public Realm getRealm(){CustomerRealm customerRealm = new CustomerRealm();
// 设置 hashed 凭证匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
// 设置 md5 加密
credentialsMatcher.setHashAlgorithmName("md5");
// 设置散列次数
credentialsMatcher.setHashIterations(1024);
customerRealm.setCredentialsMatcher(credentialsMatcher);
return customerRealm;
}
6.8 授权实现
0. 页面资源授权
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<shiro:hasAnyRoles name="user,admin">
<li><a href=""> 用户管理 </a>
<ul>
<shiro:hasPermission name="user:add:*">
<li><a href=""> 添加 </a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:delete:*">
<li><a href=""> 删除 </a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:update:*">
<li><a href=""> 修改 </a></li>
</shiro:hasPermission>
<shiro:hasPermission name="user:find:*">
<li><a href=""> 查询 </a></li>
</shiro:hasPermission>
</ul>
</li>
</shiro:hasAnyRoles>
<shiro:hasRole name="admin">
<li><a href=""> 商品管理 </a></li>
<li><a href=""> 订单管理 </a></li>
<li><a href=""> 物流管理 </a></li>
</shiro:hasRole>
1. 代码方式授权
@RequestMapping("save")
public String save(){System.out.println("进入方法");
// 获取主体对象
Subject subject = SecurityUtils.getSubject();
// 代码方式
if (subject.hasRole("admin")) {System.out.println("保存订单!");
}else{System.out.println("无权访问!");
}
// 基于权限字符串
//....
return "redirect:/index.jsp";
}
2. 方法调用授权
- @RequiresRoles 用来基于角色进行授权
- @RequiresPermissions 用来基于权限进行授权
@RequiresRoles(value={"admin","user"})// 用来判断角色 同时具有 admin user
@RequiresPermissions("user:update:01") // 用来判断权限字符串
@RequestMapping("save")
public String save(){System.out.println("进入方法");
return "redirect:/index.jsp";
}
3. 授权数据持久化
[外链图片转存失败, 源站可能有防盗链机制, 建议将图片保存下来直接上传(img-fu4xzEf1-1591145976800)(Shiro 实战教程.assets/image-20200527204839080.png)]
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for t_pers
-- ----------------------------
DROP TABLE IF EXISTS `t_pers`;
CREATE TABLE `t_pers` (`id` int(6) NOT NULL AUTO_INCREMENT,
`name` varchar(80) DEFAULT NULL,
`url` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for t_role
-- ----------------------------
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role` (`id` int(6) NOT NULL AUTO_INCREMENT,
`name` varchar(60) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for t_role_perms
-- ----------------------------
DROP TABLE IF EXISTS `t_role_perms`;
CREATE TABLE `t_role_perms` (`id` int(6) NOT NULL,
`roleid` int(6) DEFAULT NULL,
`permsid` int(6) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (`id` int(6) NOT NULL AUTO_INCREMENT,
`username` varchar(40) DEFAULT NULL,
`password` varchar(40) DEFAULT NULL,
`salt` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for t_user_role
-- ----------------------------
DROP TABLE IF EXISTS `t_user_role`;
CREATE TABLE `t_user_role` (`id` int(6) NOT NULL,
`userid` int(6) DEFAULT NULL,
`roleid` int(6) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
SET FOREIGN_KEY_CHECKS = 1;
4. 创建 dao 方法
// 根据用户名查询所有角色
User findRolesByUserName(String username);
// 根据角色 id 查询权限集合
List<Perms> findPermsByRoleId(String id);
5.mapper 实现
<resultMap id="userMap" type="User">
<id column="uid" property="id"/>
<result column="username" property="username"/>
<!-- 角色信息 -->
<collection property="roles" javaType="list" ofType="Role">
<id column="id" property="id"/>
<result column="rname" property="name"/>
</collection>
</resultMap>
<select id="findRolesByUserName" parameterType="String" resultMap="userMap">
SELECT u.id uid,u.username,r.id,r.NAME rname
FROM t_user u
LEFT JOIN t_user_role ur
ON u.id=ur.userid
LEFT JOIN t_role r
ON ur.roleid=r.id
WHERE u.username=#{username}
</select>
<select id="findPermsByRoleId" parameterType="String" resultType="Perms">
SELECT p.id,p.NAME,p.url,r.NAME
FROM t_role r
LEFT JOIN t_role_perms rp
ON r.id=rp.roleid
LEFT JOIN t_perms p ON rp.permsid=p.id
WHERE r.id=#{id}
</select>
6.Service 接口
// 根据用户名查询所有角色
User findRolesByUserName(String username);
// 根据角色 id 查询权限集合
List<Perms> findPermsByRoleId(String id);
7.Service 实现
@Override
public List<Perms> findPermsByRoleId(String id) {return userDAO.findPermsByRoleId(id);
}
@Override
public User findRolesByUserName(String username) {return userDAO.findRolesByUserName(username);
}
8. 修改自定义 realm
public class CustomerRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 获取身份信息
String primaryPrincipal = (String) principals.getPrimaryPrincipal();
System.out.println("调用授权验证:"+primaryPrincipal);
// 根据主身份信息获取角色 和 权限信息
UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
User user = userService.findRolesByUserName(primaryPrincipal);
// 授权角色信息
if(!CollectionUtils.isEmpty(user.getRoles())){SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
user.getRoles().forEach(role->{simpleAuthorizationInfo.addRole(role.getName());
// 权限信息
List<Perms> perms = userService.findPermsByRoleId(role.getId());
if(!CollectionUtils.isEmpty(perms)){
perms.forEach(perm->{simpleAuthorizationInfo.addStringPermission(perm.getName());
});
}
});
return simpleAuthorizationInfo;
}
return null;
}
}
9. 启动测试
6.9 使用 CacheManager
1.Cache 作用
- Cache 缓存: 计算机内存中一段数据
- 作用: 用来减轻 DB 的访问压力, 从而提高系统的查询效率
- 流程:
2. 使用 shiro 中默认 EhCache 实现缓存
1. 引入依赖
<!-- 引入 shiro 和 ehcache-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.5.3</version>
</dependency>
2. 开启缓存
//3. 创建自定义 realm
@Bean
public Realm getRealm(){CustomerRealm customerRealm = new CustomerRealm();
// 修改凭证校验匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
// 设置加密算法为 md5
credentialsMatcher.setHashAlgorithmName("MD5");
// 设置散列次数
credentialsMatcher.setHashIterations(1024);
customerRealm.setCredentialsMatcher(credentialsMatcher);
// 开启缓存管理器
customerRealm.setCachingEnabled(true);
customerRealm.setAuthorizationCachingEnabled(true);
customerRealm.setAuthorizationCachingEnabled(true);
customerRealm.setCacheManager(new EhCacheManager());
return customerRealm;
}
3. 启动刷新页面进行测试
- 注意: 如果控制台没有任何 sql 展示说明缓存已经开启
3.shiro 中使用 Redis 作为缓存实现
1. 引入 redis 依赖
<!--redis 整合 springboot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2. 配置 redis 连接
spring.redis.port=6379
spring.redis.host=localhost
spring.redis.database=0
3. 启动 redis 服务
➜ bin ls
dump.rdb redis-check-aof redis-cli redis-server redis.conf
redis-benchmark redis-check-rdb redis-sentinel redis-trib.rb
➜ bin ./redis-server redis.conf
4. 开发 RedisCacheManager
public class RedisCacheManager implements CacheManager {
@Override
public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {System.out.println("缓存名称:"+cacheName);
return new RedisCache<K,V>(cacheName);
}
}
5. 开 RedisCache 实现
public class RedisCache<K,V> implements Cache<K,V> {
private String cacheName;
public RedisCache() {}
public RedisCache(String cacheName) {this.cacheName = cacheName;}
@Override
public V get(K k) throws CacheException {System.out.println("获取缓存:"+ k);
return (V) getRedisTemplate().opsForHash().get(this.cacheName,k.toString());
}
@Override
public V put(K k, V v) throws CacheException {System.out.println("设置缓存 key:"+k+"value:"+v);
getRedisTemplate().opsForHash().put(this.cacheName,k.toString(),v);
return null;
}
@Override
public V remove(K k) throws CacheException {return (V) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString());
}
@Override
public v remove(k k) throws CacheException {return (v) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString());
}
@Override
public void clear() throws CacheException {getRedisTemplate().delete(this.cacheName);
}
@Override
public int size() {return getRedisTemplate().opsForHash().size(this.cacheName).intValue();}
@Override
public Set<k> keys() {return getRedisTemplate().opsForHash().keys(this.cacheName);
}
@Override
public Collection<v> values() {return getRedisTemplate().opsForHash().values(this.cacheName);
}
private RedisTemplate getRedisTemplate(){RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
// 封装获取 redisTemplate
private RedisTemplate getRedisTemplate(){RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
6. 启动项目测试发现报错
- 错误解释: 由于 shiro 中提供的 simpleByteSource 实现没有实现序列化, 所有在认证时出现错误信息
-
解决方案: 需要自动 salt 实现序列化
-
自定义 salt 实现序列化
// 自定义 salt 实现 实现序列化接口 public class MyByteSource extends SimpleByteSource implements Serializable {public MyByteSource(String string) {super(string); } }
-
在 realm 中使用自定义 salt
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {System.out.println("=========================="); // 根据身份信息 String principal = (String) token.getPrincipal(); // 在工厂中获取 service 对象 UserService userService = (UserService) ApplicationContextUtils.getBean("userService"); User user = userService.findByUserName(principal); if(!ObjectUtils.isEmpty(user)){return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(), new MyByteSource(user.getSalt()),this.getName()); } return null; }
-
7. 再次启动测试, 发现可以成功放入 redis 缓存
4. 加入验证码验证
0. 开发页面加入验证码
-
开发控制器
@RequestMapping("getImage") public void getImage(HttpSession session, HttpServletResponse response) throws IOException { // 生成验证码 String code = VerifyCodeUtils.generateVerifyCode(4); // 验证码放入 session session.setAttribute("code",code); // 验证码存入图片 ServletOutputStream os = response.getOutputStream(); response.setContentType("image/png"); VerifyCodeUtils.outputImage(220,60,os,code); }
- 放行验证码
- 开发页面
-
修改认证流程
@RequestMapping("login") public String login(String username, String password,String code,HttpSession session) { // 比较验证码 String codes = (String) session.getAttribute("code"); try {if (codes.equalsIgnoreCase(code)){ // 获取主体对象 Subject subject = SecurityUtils.getSubject(); subject.login(new UsernamePasswordToken(username, password)); return "redirect:/index.jsp"; }else{throw new RuntimeException("验证码错误!"); } } catch (UnknownAccountException e) {e.printStackTrace(); System.out.println("用户名错误!"); } catch (IncorrectCredentialsException e) {e.printStackTrace(); System.out.println("密码错误!"); }catch (Exception e){e.printStackTrace(); System.out.println(e.getMessage()); } return "redirect:/login.jsp"; }
-
修改 salt 不能序列化的问题
// 自定义 salt 实现 实现序列化接口 public class MyByteSource implements ByteSource,Serializable {private byte[] bytes; private String cachedHex; private String cachedBase64; // 加入无参数构造方法实现序列化和反序列化 public MyByteSource(){} public MyByteSource(byte[] bytes) {this.bytes = bytes;} public MyByteSource(char[] chars) {this.bytes = CodecSupport.toBytes(chars); } public MyByteSource(String string) {this.bytes = CodecSupport.toBytes(string); } public MyByteSource(ByteSource source) {this.bytes = source.getBytes(); } public MyByteSource(File file) {this.bytes = (new MyByteSource.BytesHelper()).getBytes(file); } public MyByteSource(InputStream stream) {this.bytes = (new MyByteSource.BytesHelper()).getBytes(stream); } public static boolean isCompatible(Object o) {return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;} public byte[] getBytes() {return this.bytes;} public boolean isEmpty() {return this.bytes == null || this.bytes.length == 0;} public String toHex() {if (this.cachedHex == null) {this.cachedHex = Hex.encodeToString(this.getBytes()); } return this.cachedHex; } public String toBase64() {if (this.cachedBase64 == null) {this.cachedBase64 = Base64.encodeToString(this.getBytes()); } return this.cachedBase64; } public String toString() {return this.toBase64(); } public int hashCode() {return this.bytes != null && this.bytes.length != 0 ? Arrays.hashCode(this.bytes) : 0; } public boolean equals(Object o) {if (o == this) {return true;} else if (o instanceof ByteSource) {ByteSource bs = (ByteSource)o; return Arrays.equals(this.getBytes(), bs.getBytes()); } else {return false;} } private static final class BytesHelper extends CodecSupport {private BytesHelper() { } public byte[] getBytes(File file) {return this.toBytes(file); } public byte[] getBytes(InputStream stream) {return this.toBytes(stream); } } }