SpringBoot Shiro 实现登录/记住我的功能
导读
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属性信息也是要留意过滤器配置,不然是无法生效的,如上流程碰到问题,欢迎大家与我沟通。