SpringBoot Shiro 实现登录/记住我的功能

createh53周前 (02-24)技术教程5

导读

Apache Shiro 是一个简单且强大的框架。 提供了全面的安全管理服务、认证、授权、加密和会话管理等功能,相对于其他安全框架,Shiro 使用简单,上手快,也是受到了很多企业的青睐。本教程会使用Springboot + Shiro + mybatis-plus +thymeleaf+lombok实现开发流程。

创建一个hsb-admin项目

依赖包


        org.springframework.boot
        spring-boot-starter-parent
        2.2.6.RELEASE
         

  	 3.3.2
     1.1.22
  
       
  		
       
            org.projectlombok
            lombok
            true
		

			
      
                com.alibaba
                druid-spring-boot-starter
                ${druid-spring-boot-starter.version}
       

     
		
                com.baomidou
                mybatis-plus-boot-starter
                ${mybatis-plus-boot-starter.version}
         

	  
	  	org.springframework.boot
	  	spring-boot-starter-web
	  

    		 
		
            org.springframework.boot
            spring-boot-starter-thymeleaf
    

     
		
			mysql
			mysql-connector-java
		

        
		
		    org.apache.shiro
		    shiro-spring-boot-starter
		    1.6.0
		

   
		
			com.github.theborakompanioni
			thymeleaf-extras-shiro
			2.0.0
		

  

编辑application.yml配置

server:
  port: 7010

spring:
  application:
    # 服务名,在注册时所用,调用方所用 
    name: hsb-admin
  mvc:
    #静态目录配置
    static-path-pattern: /static/** 

  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.0.103:3306/hsb?serverTimezone=UTC&useUnicode=true&zeroDateTimeBehavior=convertToNull&characterEncoding=UTF-8&autoReconnect=true&&useSSL=false
    username: root
    password: 123456
    
    druid: 
      #初始化链接数大小
      initial-size: 5
      #最大链接数大小
      max-active: 50
      #空闲链接数大小
      min-idle: 2
      #链接超时时间,单位ms
      max-wait: 50000
  thymeleaf:
    cache: false

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

新建HsbAdminApp启动程序

/**
 * 后台项目
 */
@SpringBootApplication(scanBasePackages = "com.hsb")
@MapperScan(basePackages = "com.hsb.admin.dao")//指定扫描dao接口
public class HsbAdminApp 
{
    public static void main( String[] args )
    {
        SpringApplication.run(HsbAdminApp.class, args);
    }
    
}

新建SysUserPO

该类就是用户登录PO类

@Data
@EqualsAndHashCode(callSuper = false)
@TableName("sys_user")
public class SysUserPO implements Serializable {

    private static final long serialVersionUID=1L;
    
    Long id;//主键ID',
    String username;//'用户名',
    String password;//'密码',
    Integer status;//'状态:0禁用,1启用',
    Date createTime;//'创建时间',
    Date updateTime;//'更新时间',
    Long createrId;//'创建者',
}

新建SysUserDAO/SysUserService这两个类大家自行创建,里面都是新增改查逻辑,部分代码如下

/**
 * 基础Dao,所有Dao都要继承该类
 */
public interface BaseDAO extends BaseMapper,Mapper{
}

/**
 * 系统用户账号  DAO
 */
public interface SysUserDAO extends BaseDAO {

}
/**
 * 系统用户业务 Service
 */
@Service
public class SysUserService {
	
	@Autowired
	SysUserDAO sysUserDAO;

	/**
	 *   获取对象
	 * @param sysUserPO
	 * @return
	 */
	public SysUserPO get(SysUserPO sysUserPO) {
		//查询返回用户
		QueryWrapper queryWrapper=new QueryWrapper(sysUserPO);
		return sysUserDAO.selectOne(queryWrapper);
	}
}

新建LoginController

该负责用Shiro执行登录,登录错误提示,登录成功逻辑

/**
 * 登录控制器
 */
@Controller
@Slf4j
public class LoginController {

	/**
	 * 登录页面
	 * @return
	 */
	@GetMapping("login")
	public String login() {
		return "login";
	}
	
	/**
	 * 执行登录
	 * @param loginReqVO
	 * @return
	 */
	@PostMapping("login")
	@ResponseBody
	public ResponseVO login(LoginReqVO loginReqVO){
		
		try {
		log.info("登录信息={}",loginReqVO);	
		
		//shiro 从安全工具类,获取一个Subject,代表了当前 “用户”
		//所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager
		Subject subject = SecurityUtils.getSubject();
			
		//创建一个认证凭证,且这个认证凭证是用户名,密码转入到shiro执行登录认证
		UsernamePasswordToken authenticationToken=new UsernamePasswordToken(loginReqVO.getUsername(), loginReqVO.getPassword());
		//是否记住登录
		authenticationToken.setRememberMe(loginReqVO.isRememberMe());	
		//执行登录
		subject.login(authenticationToken);
		
		//代码走到这里,说明登录成功了
		
		SysUserPO sysUserPO=(SysUserPO)subject.getPrincipal();
		log.info("登录成功,从shrio获取到已成功用户对象信息={}",sysUserPO);
		
		}catch (UnknownAccountException e) {//不存在账号抛出异常
			log.error(e.getMessage());;
			return ResponseVO.fail("用户不存在");
		}catch (DisabledAccountException e) {//账号被禁用
			log.error(e.getMessage());;
			return ResponseVO.fail("用户被禁用,请联系管理员");
		}catch (IncorrectCredentialsException e) {//异常说明,不正确的凭证,意思就是密码错误
			log.error(e.getMessage());;
			return ResponseVO.fail("密码错误");
		}catch (RuntimeException e) {//运行时,自定义抛出异常
			log.error("自定义错误异常",e);
			return ResponseVO.fail(e.getMessage());
		}
		
		return ResponseVO.success(null);
	}
	
	/**
	 *  退出登录
	 * @return
	 */
	@GetMapping("loginout")
	public String loginout() {
		Subject subject=SecurityUtils.getSubject();
		SysUserPO sysUserPO=(SysUserPO)subject.getPrincipal();
		subject.logout();
		log.info("退出登录={}",sysUserPO.getUsername());
		return "login";
	}
}

新建SysUserRealm类

/**
 * Realm域,Shiro 从从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份
* 1、那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法
* 2、需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作
*/ @Slf4j @Configuration public class SysUserRealm extends AuthenticatingRealm{ @Autowired SysUserService sysUserService; /** * 身份认证 / 登录,验证用户是不是拥有相应的身份
* SecurityUtils.getSubject().login()执行后--->进入这个方法执行具体登录校验
* */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //得到用户密码token凭证 UsernamePasswordToken usernamePasswordToken=(UsernamePasswordToken)token; //获得登录用户名,密码 String userName=usernamePasswordToken.getUsername(); log.info("登录授权,username={}",userName); //根据用户名查询用户对象 SysUserPO sysUserPO=new SysUserPO(); sysUserPO.setUsername(userName); sysUserPO=sysUserService.get(sysUserPO); //如果等于null,说明用户不存在。 //问题如何返回用户提示不存在呢? if(sysUserPO==null) { //通过抛出异常来解决提示错误信息 throw new UnknownAccountException("用户不存在=".concat(userName)); //自定义抛出异常,如果不是AuthenticationException子类,那么异常信息不会向上抛出,因此自定义异常要继承这个类,Shiro提供的默认异常基本够用了 //throw new RuntimeException("用户不存在"); } //如果账号状态是禁用状态 if(new Integer(0).equals(sysUserPO.getStatus())){ throw new DisabledAccountException("用户被禁用=".concat(userName)); } //账号正常,校验登录密码 //第一个参数,存返回用户对象,第二个参数存密码,第三个登录用户名 SimpleAuthenticationInfo simpleAuthenticationInfo=new SimpleAuthenticationInfo(sysUserPO, sysUserPO.getPassword(), sysUserPO.getUsername()); //返回登录授权信息,shiro会自动校验转入的密码与数据库返回的密码是否一致,如果一致,代表登录成功 return simpleAuthenticationInfo; } }

新建ShiroConfig类

/**
 * Shiro配置类
* 该类主要初始化启动装载Shiro类对象,完成相关实例化工作,为后面功能提供服务 */ @Configuration public class ShiroConfig { /** * 实例化一个Shiro安全管理器,相当于 SpringMVC 中的 DispatcherServlet
* 所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理
* @param userRealm 注入用户认证,权限安全域 * @return */ @Bean public DefaultWebSecurityManager securityManager(SysUserRealm userRealm) { DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager(); //设置用户域 defaultSecurityManager.setRealm(userRealm); //设置记住密码管理 defaultSecurityManager.setRememberMeManager(cookieRememberMeManager()); //创建了一个session管理器 defaultSecurityManager.setSessionManager(defaultWebSessionManager()); ThreadContext.bind(defaultSecurityManager);//解决异常:ThreadContext or as a vm static singleton return defaultSecurityManager; } /** * 配置shiro过滤器后,相当于servlet的filter
* 1、页面才能正常使用shiro相关标签,不然使用无效,如< shiro:principal property="username"/>
* 2、其他类获取SysUserPO sysUserPO=(SysUserPO)SecurityUtils.getSubject().getPrincipal();才不会报错
* @param securityManager * @return */ @Bean(name = "shiroFilter") public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) { //创建一个过滤工厂Bean,跟我们以前web用的过滤器类似 ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //注入安全管理类 shiroFilterFactoryBean.setSecurityManager(securityManager); //设置登录页面路径 shiroFilterFactoryBean.setLoginUrl("/login"); //设置没有权限访问进入路径 shiroFilterFactoryBean.setUnauthorizedUrl("/notAuth"); //创建一个过滤路径map对象,key是路径,value值说明:anon代表匿名访问,authc代表必须登录subject.login认证后授权后才能访问 Map filterChainDefinitionMap = new LinkedHashMap<>(); //放开static静态文件路径不授权就可以访问,如果不放开,相关js,css将无法访问 filterChainDefinitionMap.put("/static/**", "anon"); //放开执行登录操作 filterChainDefinitionMap.put("/login", "anon"); //退出登录操作 filterChainDefinitionMap.put("/loginout", "logout"); //authc其他所有路径都必须通过subject.login认证后才能访问 //filterChainDefinitionMap.put("/**", "authc"); //如果用了rememberMe记住我功能,那就必须把authc改成user,否则记住我功能失效,必须要记住了 filterChainDefinitionMap.put("/**", "user"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } /** * Shiro 整合thymeleaf框架,加这个注解后才能使用标签,如
* < shiro:principal property="username"/>
* @return */ @Bean public ShiroDialect shiroDialect() { return new ShiroDialect(); } //<===============RememberMe配置==================> /** * Cookie记住我管理器 * @return */ @Bean public RememberMeManager cookieRememberMeManager() { CookieRememberMeManager cookieRememberMeManager=new CookieRememberMeManager(); //rememberMe cookie加密的密钥 默认AES算法 密钥长度(128 256 512 位) //6BvVHd5gUs0FEA86DFAdAg== 是密码钥匙,建议每个项目都不一样 cookieRememberMeManager.setCipherKey(Base64.decode("6BvVHd5gUs0FEA86DFAdAg==")); //设置cookie cookieRememberMeManager.setCookie(simpleCookie()); return cookieRememberMeManager; } /** * 自定义指定cookie多少天,如果没有操作的情况下自动过期 * @return */ @Bean public SimpleCookie simpleCookie() { //创建一个cookie名称 SimpleCookie simpleCookie=new SimpleCookie("rememberMe"); simpleCookie.setMaxAge(2592000);//单位秒,折算成30天 return simpleCookie; } /** * 创建一个session管理器,解决重定向自动带有;jsessionid=0935FAEB578E70CDB29AA08303F91C69 * @return */ @Bean public DefaultWebSessionManager defaultWebSessionManager() { DefaultWebSessionManager defaultWebSessionManager=new DefaultWebSessionManager(); //移除重定向不自动带有;jsessionid=0935FAEB578E70CDB29AA08303F91C69功能 defaultWebSessionManager.setSessionIdUrlRewritingEnabled(false); return defaultWebSessionManager; } }

新建登录页面

登录login.html页面,登录成功后进入后台主页,没有登录,无法进入主页




    
    
    
    有贝口腔网-登录
    
    
    


登录来到后台登录页面
账号:
密码:
记住我:

新建后台IndexController

/**
 * 后台主页
 */
@Controller
@Slf4j
public class IndexController {
	/**
	 *   进入首页页面
	 * @return
	 */
	@GetMapping("/")
	public String add() {
		 //测试从shiro获取登录用户信息
		 SysUserPO sysUserPO=(SysUserPO)SecurityUtils.getSubject().getPrincipal();
		 log.info("进入主页,获取到登录用户信息={}",sysUserPO);
		return "index";
	}
}

主页index.html页面





管理主页


欢迎来到后台管理系统
您好: 退出登录

系统用户管理
注意说明: 标签显示登录用户名; (1)他是等价于:String username=((SysUserPO)SecurityUtils.getSubject().getPrincipal()).getUsername();//获取这个值 (2)filterChainDefinitionMap.put("/**", "user"); //一定要过滤器配置"/** "拦截所有路径,才能显示生效

登录代码流程

启动服务

测试访问, 这样就可以实现了用Shiro登录功能了。

测试一:不登录,直接访问主页,那么系统就会自动跳转到登录页面
http://127.0.0.1:7010/

测试二:登录页面,登录后进入主页,主页显示登录用户名
http://127.0.0.1:7010/login    

测试三:测试登录记住我功能,看下面流程

RememberMe记住我

RememberMe记住我是Shiro 提供的一项功能,比如访问某些网站时,关闭了浏览器下次再打开时还是能记住你是谁,下次访问时无需再登录即可访问。如果是浏览器登录,一般会把 RememberMe 的 Cookie 写到客户端并保存下来。

1、登录LoginController设置记住代码

2、配置ShiroConfig

(1)配置RememberMeManager记住管理器,注意加密秘钥key,用到的AES对着加密技术实现

(2)配置SimpleCookie自定义记住有效天数

(3)修改ShiroFilterFactoryBean过滤权限,要使用user不要使用authc,具体说明看代码说明

3、最后重启服务,测试记住我功能,不管你关闭浏览器或者重启服务,再次直接访问页面都是可以直接访问了,不需要进入登录页面。

登录页面jsessionid问题

如果你测试记住我公,出现如下登录页面异常

http://127.0.0.1:7010/login;jsessionid=0935FAEB578E70CDB29AA08303F91C69

页面显示如下
This application has no explicit mapping for /error, so you are seeing this as a fallback.

Thu Nov 19 19:58:43 CST 2020
There was an unexpected error (type=Bad Request, status=400).
Invalid request

解决方式加入DefaultWebSessionManager如下图


总结

本教程讲解了springboot+shiro实现登录,记住我功能,很简单地实现了这些功能。同时也碰到一些细节问题,如记住我功能,都是要注意过滤器配置;页面用标签显示shiro属性信息也是要留意过滤器配置,不然是无法生效的,如上流程碰到问题,欢迎大家与我沟通。

相关文章

那些年的QQ登录界面,你还记得吗,满满的全是回忆

刚上大学的时候,手机QQ软件不是很流行,因为除了一些品牌手机,其他国产手机基本不支持这个软件。那时候手机上网也是刚刚开始流行,5元30M流量,省着点用,是能坚持到月底的。那时候喜欢跟人聊qq,就用网页...

使用Flutter设计一个好看的&quot;我&quot;页面

近期遇到一些很烦的琐事,状态比较down,很多原本计划好的事情都耽搁了,实在是难顶……看到后台一直有朋友问怎么博客和公众号没有更新,所以我忙完得闲就来更了!前言起因是最近重拾以前的旧项目(业余做的,打...

开始使用支付宝登录接口之前

开始使用支付宝登录接口之前,您需要先创建一个支付宝应用并将其注册到支付宝。这将为您提供一个应用 ID 和应用密钥,您需要在调用 API 时使用它们。获取用户授权要使用支付宝登录接口,您需要先从用户获取...

写了这么多年代码,这样的登录方式还是头一回见

Spring Security 系列还没搞完,最近还在研究。有的时候我不禁想,如果从 Spring Security 诞生的第一天开始,我们就一直在追踪它,那么今天再去看它的源码一定很简单,因为我们了...

用友NC系统“登陆后页面显示不完全”,怎么办?

用友NC系统使用过程中常见问题和解决方法:1、无法安装客户端插件,不能进入NC系统登陆界面问题现象现象1:可以打开web界面,但无法进入登陆界面,一直停留在右图所示界面。现象2:系统提示安全警告问题原...

「JWT」,你必须了解的认证登录方案

「JWT」,你必须了解的认证登录方案

作者:古时的风筝原文链接:https://www.cnblogs.com/fengzheng/p/13527425.htmlJWT 全称是 JSON Web Token,是目前非常流行的跨域认证解决方...