Shiro + redis + 登录 + 记住我 + 验证码 + 登出(mysiteforme)
从访问开始
http://localhost:8080
@GetMapping(value = "") public String index() { LOGGER.info("这事空地址在请求路径"); Subject s = SecurityUtils.getSubject(); return s.isAuthenticated() ? "redirect:index" : "login"; }
登录以后结果就是true ,否则去登录
<input name="username" placeholder="用户名"> <input name="password" placeholder="密码" type="password">
<input type="checkbox" name="rememberMe" value="true" lay-skin="primary" checked title="记住帐号?">
<input name="code" placeholder="验证码" type="text" > <img src="${base}/genCaptcha" width="116" height="36" id="mycode">
图片验证码
{base} 来源于拦截器
生产验证码
/** * 获取验证码图片和文本(验证码文本会保存在HttpSession中) */ @GetMapping("/genCaptcha") public void genCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException { //设置页面不缓存 response.setHeader("Pragma", "no-cache"); response.setHeader("Cache-Control", "no-cache"); response.setDateHeader("Expires", 0); String verifyCode = VerifyCodeUtil.generateTextCode(VerifyCodeUtil.TYPE_ALL_MIXED, 4, null); //将验证码放到HttpSession里面 request.getSession().setAttribute(Constants.VALIDATE_CODE, verifyCode); LOGGER.info("本次生成的验证码为[" + verifyCode + "],已存放到HttpSession中"); //设置输出的内容的类型为JPEG图像 response.setContentType("image/jpeg"); BufferedImage bufferedImage = VerifyCodeUtil.generateImageCode(verifyCode, 116, 36, 5, true, new Color(249,205,173), null, null); //写给浏览器 ImageIO.write(bufferedImage, "JPEG", response.getOutputStream()); }
登录请求:
<form action="${base}/login/main" method="post">
<button class="layui-btn login_btn" lay-submit="" lay-filter="login">登录</button>
@PostMapping("login/main") @ResponseBody @SysLog("用户登录") public RestResponse loginMain(HttpServletRequest request) { String username = request.getParameter("username"); String password = request.getParameter("password"); String rememberMe = request.getParameter("rememberMe"); String code = request.getParameter("code"); if(StringUtils.isBlank(username) || StringUtils.isBlank(password)){ return RestResponse.failure("用户名或者密码不能为空"); } if(StringUtils.isBlank(rememberMe)){ return RestResponse.failure("记住我不能为空"); } if(StringUtils.isBlank(code)){ return RestResponse.failure("验证码不能为空"); } Map<String,Object> map = Maps.newHashMap(); String error = null; HttpSession session = request.getSession(); if(session == null){ return RestResponse.failure("session超时"); } String trueCode = (String)session.getAttribute(Constants.VALIDATE_CODE); if(StringUtils.isBlank(trueCode)){ return RestResponse.failure("验证码超时"); } if(StringUtils.isBlank(code) || !trueCode.toLowerCase().equals(code.toLowerCase())){ error = "验证码错误"; }else { /*就是代表当前的用户。*/ Subject user = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(username,password,Boolean.valueOf(rememberMe)); try { user.login(token); if (user.isAuthenticated()) { map.put("url","index"); } }catch (IncorrectCredentialsException e) { error = "登录密码错误."; } catch (ExcessiveAttemptsException e) { error = "登录失败次数过多"; } catch (LockedAccountException e) { error = "帐号已被锁定."; } catch (DisabledAccountException e) { error = "帐号已被禁用."; } catch (ExpiredCredentialsException e) { error = "帐号已过期."; } catch (UnknownAccountException e) { error = "帐号不存在"; } catch (UnauthorizedException e) { error = "您没有得到相应的授权!"; } } if(StringUtils.isBlank(error)){ return RestResponse.success("登录成功").setData(map); }else{ return RestResponse.failure(error); } }
登出
@SysLog("退出系统") public String logOut(){ SecurityUtils.getSubject().logout(); return "redirect:/login"; }
String error = null; user.login(token); if (user.isAuthenticated()) { map.put("url","index"); } if(StringUtils.isBlank(error)){ return RestResponse.success("登录成功").setData(map); }else{ return RestResponse.failure(error); }
如果登录成功会跳转到index.htlm
否则返回错误信息
看一下bean RestResponse
public class RestResponse extends HashMap<String, Object> { public static RestResponse success(){ return success("成功"); } public static RestResponse success(String message){ RestResponse restResponse = new RestResponse(); restResponse.setSuccess(true); restResponse.setMessage(message); return restResponse; } public static RestResponse failure(String message){ RestResponse restResponse = new RestResponse(); restResponse.setSuccess(false); restResponse.setMessage(message); return restResponse; } public RestResponse setSuccess(Boolean success) { if (success != null) put("success", success); return this; } public RestResponse setMessage(String message) { if (message != null) put("message", message); return this; } public RestResponse setData(Object data) { if (data != null) put("data", data); return this; }
二、shiro 配置
2.1 shiro - rdis 配置
@Configuration public class ShiroConfig { private Logger logger = LoggerFactory.getLogger(ShiroConfig.class); @Value("${spring.redis.host}") private String jedisHost; @Value("${spring.redis.port}") private Integer jedisPort; @Value("${spring.redis.password}") private String jedisPassword; @Bean public FilterRegistrationBean delegatingFilterProxy(){ FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); DelegatingFilterProxy proxy = new DelegatingFilterProxy(); proxy.setTargetFilterLifecycle(true); proxy.setTargetBeanName("shiroFilter"); filterRegistrationBean.setFilter(proxy); filterRegistrationBean.setDispatcherTypes(DispatcherType.ERROR,DispatcherType.REQUEST,DispatcherType.FORWARD,DispatcherType.INCLUDE); return filterRegistrationBean; } @Bean(name = "shiroFilter") public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("authRealm")AuthRealm authRealm){ ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); bean.setSecurityManager(securityManager(authRealm)); bean.setSuccessUrl("/index"); bean.setLoginUrl("/login"); Map<String,Filter> map = Maps.newHashMap(); map.put("authc",new CaptchaFormAuthenticationFilter()); bean.setFilters(map); //配置访问权限 LinkedHashMap<String, String> filterChainDefinitionMap = Maps.newLinkedHashMap(); filterChainDefinitionMap.put("/static/**","anon"); filterChainDefinitionMap.put("/showBlog/**","anon"); filterChainDefinitionMap.put("/blog/**","anon"); filterChainDefinitionMap.put("/login/main","anon"); filterChainDefinitionMap.put("/genCaptcha","anon"); filterChainDefinitionMap.put("/systemLogout","authc"); filterChainDefinitionMap.put("/**","authc"); bean.setFilterChainDefinitionMap(filterChainDefinitionMap); return bean; } @Bean public SecurityManager securityManager(@Qualifier("authRealm")AuthRealm authRealm){ logger.info("- - - - - - -shiro开始加载- - - - - - "); DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); defaultWebSecurityManager.setRealm(authRealm); defaultWebSecurityManager.setRememberMeManager(rememberMeManager()); defaultWebSecurityManager.setSessionManager(webSessionManager()); defaultWebSecurityManager.setCacheManager(cacheManager()); return defaultWebSecurityManager; } @Bean public SimpleCookie rememberMeCookie(){ //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe SimpleCookie cookie = new SimpleCookie("rememberMe"); cookie.setHttpOnly(true); //记住我有效期长达30天 cookie.setMaxAge(2592000); return cookie; } @Bean public CookieRememberMeManager rememberMeManager(){ CookieRememberMeManager rememberMeManager = new CookieRememberMeManager(); rememberMeManager.setCookie(rememberMeCookie()); rememberMeManager.setCipherKey(Base64.decode("2AvVhdsgUs0FSA3SDFAdag==")); return rememberMeManager; } /** * AOP式方法级权限检查 * @return */ @Bean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreator creator=new DefaultAdvisorAutoProxyCreator(); creator.setProxyTargetClass(true); return creator; } /** * 保证实现了Shiro内部lifecycle函数的bean执行 * @return */ @Bean public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){ return new LifecycleBeanPostProcessor(); } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("authRealm")AuthRealm authRealm) { SecurityManager manager= securityManager(authRealm); AuthorizationAttributeSourceAdvisor advisor=new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(manager); return advisor; } @Bean public SessionManager webSessionManager(){ DefaultWebSessionManager manager = new DefaultWebSessionManager(); //设置session过期时间为1小时(单位:毫秒),默认为30分钟 manager.setGlobalSessionTimeout(60 * 60 * 1000); manager.setSessionValidationSchedulerEnabled(true); manager.setSessionDAO(redisSessionDAO()); return manager; } @Bean public RedisManager redisManager(){ RedisManager manager = new RedisManager(); manager.setHost(jedisHost); manager.setPort(jedisPort); //这里是用户session的时长 跟上面的setGlobalSessionTimeout 应该保持一直(上面是1个小时 下面是秒做单位的 我们设置成3600) manager.setExpire(60 * 60); manager.setPassword(jedisPassword); return manager; } @Bean public RedisSessionDAO redisSessionDAO(){ RedisSessionDAO sessionDAO = new RedisSessionDAO(); sessionDAO.setKeyPrefix("wl_"); sessionDAO.setRedisManager(redisManager()); return sessionDAO; } @Bean("myCacheManager") public RedisCacheManager cacheManager(){ RedisCacheManager manager = new RedisCacheManager(); manager.setRedisManager(redisManager()); return manager; } }
启用redis 缓存
@EnableCaching @Configuration public class RedisCacheConfig { @Bean public RedisTemplate redisTemplate(RedisConnectionFactory factory) { StringRedisTemplate redisTemplate = new StringRedisTemplate(factory); // JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer(); //这里如果启用fastjson序列化对象到redis的话 启动必须加参数 -Dfastjson.parser.autoTypeSupport=true // RedisSerializer fastJson = fastJson2JsonRedisSerializer(); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); redisTemplate.afterPropertiesSet(); return redisTemplate; } @Bean public CacheManager cacheManager(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) { return new RedisCacheManager(redisTemplate); } }
认证和授权
@Component(value = "authRealm") public class AuthRealm extends AuthorizingRealm { @Autowired @Lazy private UserService userService; @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { ShiroUser shiroUser = (ShiroUser)principalCollection.getPrimaryPrincipal(); User user = userService.findUserByLoginName(shiroUser.getloginName()); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); Set<Role> roles = user.getRoleLists(); Set<String> roleNames = Sets.newHashSet(); for (Role role : roles) { if(StringUtils.isNotBlank(role.getName())){ roleNames.add(role.getName()); } } Set<Menu> menus = user.getMenus(); Set<String> permissions = Sets.newHashSet(); for (Menu menu : menus) { if(StringUtils.isNotBlank(menu.getPermission())){ permissions.add(menu.getPermission()); } } info.setRoles(roleNames); info.setStringPermissions(permissions); return info; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; String username = (String)token.getPrincipal(); User user = userService.findUserByLoginName(username); if(user == null) { throw new UnknownAccountException();//没找到帐号 } if(Boolean.TRUE.equals(user.getLocked())) { throw new LockedAccountException(); //帐号锁定 } byte[] salt = Encodes.decodeHex(user.getSalt()); SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( new ShiroUser(user.getId(),user.getLoginName(),user.getNickName(), user.getIcon()), user.getPassword(), //密码 ByteSource.Util.bytes(salt), getName() //realm name ); return authenticationInfo; }
密码处理
/** * Hex编码. */ public static String encodeHex(byte[] input) { return new String(Hex.encodeHex(input)); } /** * Hex解码. */ public static byte[] decodeHex(String input) { try { return Hex.decodeHex(input.toCharArray()); } catch (DecoderException e) { throw Exceptions.unchecked(e); } }