shiro用户认证
springboot集成shiro的用户认证过程如下:
- 定义一个ShiroConfig类,实现Shiro过滤器工厂类ShiroFilterFactoryBean方法,该方法主要用于处理拦截资源文件,依赖SecurityManager。
- SecurityManager为Shiro的安全管理器,管理着所有Subject,主要有身份认证管理,缓存管理,cookie管理等。
- realm,用于身份认证和权限验证,需要实现doGetAuthorizationInfo() 和 doGetAuthenticationInfo() 方法。
引入相关依赖
引入shiro、数据源,mybatis依赖,
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- shiro-spring -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<!-- druid数据源驱动 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
定义shiroconfig类
@Configuration
public class ShiroConfig {
/**
* springboot之shiro用户认证 处理拦截资源文件问题。
* 注意:单独一个ShiroFilterFactoryBean配置是报错的,因为在 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
*
Filter Chain定义说明
1、一个URL可以配置多个Filter,使用逗号分隔
2、当设置多个过滤器时,全部验证通过,才视为通过
3、部分过滤器可指定参数,如perms,roles
*
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/index");
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// anon:所有url都都可以匿名访问; authc: 需要认证才能进行访问; user:配置记住我或认证通过可以访问;
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/fonts/**", "anon");
filterChainDefinitionMap.put("/img/**", "anon");
filterChainDefinitionMap.put("/druid/**", "anon");
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm());
return securityManager;
}
@Bean
public ShiroRealm shiroRealm(){
ShiroRealm shiroRealm = new ShiroRealm();
return shiroRealm;
}
}
过滤链filterchain基于短路机制,是最先匹配原则;
anon:所有url都可以匿名访问;
authc: 需要认证才能进行访问;
user:配置记住我或认证通过可以访问;
其中,shiro已实现anon,authc…
FilterNmae | class | description |
---|---|---|
anon | org.apache.shiro.web.filter.authc.AnonymousFilter | 匿名拦截器,即不需要登录即可访问;一般用于静态资源过滤;示例 /static/**=anon |
authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter | 基于表单的拦截器;如 /**=authc ,如果没有登录会跳到相应的登录页面登录 |
user | org.apache.shiro.web.filter.authc.UserFilter | 用户拦截器,用户已经身份验证/记住我登录的都可;示例 /**=user |
authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter | Basic HTTP身份验证拦截器 |
logout | org.apache.shiro.web.filter.authc.LogoutFilter | 退出拦截器,主要属性:redirectUrl:退出成功后重定向的地址(/),示例 /logout=logout |
role | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter | 角色授权拦截器,验证用户是否拥有所有角色;示例 /admin/**=roles[admin] |
noSessionCreation | org.apache.shiro.web..filter.session.NoSessionCreationFilter | 不创建会话拦截器,调用 subject.getSession(false) 不会有什么问题,但是如果 subject.getSession(true) 将抛出 DisabledSessionException 异常 |
perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter | 权限授权拦截器,验证用户是否拥有所有权限;属性和roles一样;示例 /user/**=perms[“user:create”] |
port | org.apache.shiro.web.filter.authz.PortFilter | 端口拦截器,主要属性 port(80) :可以通过的端口;示例 /test= port[80] ,如果用户访问该页面是非80,将自动将请求端口改为80并重定向到该80端口,其他路径/参数等都一样 |
rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter | rest风格拦截器,自动根据请求方法构建权限字符串;示例 /users=rest[user] ,会自动拼出user:read,user:create,user:update,user:delete权限字符串进行权限匹配(所有都得匹配,isPermittedAll) |
ssl | org.apache.shiro.web.filter.authz.SslFilter | SSL拦截器,只有请求协议是https才能通过;否则自动跳转会https端口443;其他和port拦截器一样; |
Realm
自定义Realm实现只需继承AuthorizingRealm类,然后实现doGetAuthorizationInfo()和doGetAuthenticationInfo()方法即可。这两个方法名乍看有点像,doGetAuthorizationInfo()获取用户的角色和权限等信息,doGetAuthenticationInfo()进行身份验证,即登录时验证用户的合法性,比如验证用户名和密码。
Shiro的认证过程会交由Realm执行,会调用Realm的getAuthenticationInfo(token)方法。
该方法主要执行逻辑如下:
1、检查提交的进行认证的令牌信息
2、根据令牌信息从数据源(通常为数据库)中获取用户信息
3、对用户信息进行匹配验证。
4、验证通过将返回一个封装了用户信息的AuthenticationInfo实例。
5、验证失败则抛出AuthenticationException异常信息。
/**
* 登录认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String userName = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials()) ;
// password= MD5Utils.encrypt(userName,password);
System.out.println("用户" + userName + "认证-----ShiroRealm.doGetAuthenticationInfo" +"password: "+password);
User user = userMapper.findByUserName(userName);
if(user == null){
throw new UnknownAccountException("用户名或密码错误!");
}
if (!password.equals(user.getPassword())) {
throw new IncorrectCredentialsException("用户名或密码错误!");
}
if (user.getStatus().equals("0")) {
throw new LockedAccountException("账号已被锁定,请联系管理员!");
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
return info;
}
配置文件application.yml
spring:
datasource:
# 数据库访问配置, 使用druid数据源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/shiro
username: root
password: root
# 连接池配置
initial-size: 5
min-idle: 5
max-active: 20
# 连接等待超时时间
max-wait: 30000
# 配置检测可以关闭的空闲连接间隔时间
time-between-eviction-runs-millis: 60000
# 配置连接在池中的最小生存时间
min-evictable-idle-time-millis: 300000
validation-query: select '1' from dual
test-while-idle: true
test-on-borrow: false
test-on-return: false
thymeleaf:
cache: false
mybatis:
# type-aliases扫描路径
type-aliases-package: com.springboot.shiro.entity
# mapper xml实现扫描路径
mapper-locations: classpath:mapper/*.xml
property:
order: BEFORE
定义数据库
创建t_user表
USE shiro
;
/*Table structure for table `t_user` */
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`id` int(11) NOT NULL,
`username` varchar(25) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`create_time` date DEFAULT NULL,
`status` varchar(4) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Data for the table `t_user` */
insert into `t_user`(`id`,`username`,`password`,`create_time`,`status`) values (1,'admin','9f953fb5518da53fe1dc83f47861cb6d','2019-04-11','1'),(2,'test','d655eb5f87c32223e3d037b760a0341d','2019-04-11','0');
定义实体类
定义实体user类
public class User {
private Integer id;
private String userName;
private String password;
private Date createTime;
private String status;
//get/set方法
}
定义logincontroller类
@PostMapping("/login")
@ResponseBody
public ActionResult login(String username,String password){
password = MD5Utils.encrypt(username,password);
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
return ActionResult.ok();
} catch (UnknownAccountException e) {
return ActionResult.error(e.getMessage());
} catch (IncorrectCredentialsException e) {
return ActionResult.error(e.getMessage());
} catch (LockedAccountException e) {
return ActionResult.error(e.getMessage());
}catch (AuthenticationException e) {
return ActionResult.error("认证失败");
}
}
编写页面login.html、index.html页面等。
启动项目访问:
地址:http://localhost:8080/login
账号或密码错误提示:
账号锁定提示:
登录成功后直接跳转到index界面。
rememberMe功能的实现
shiro提供了rememberMe功能,就是在登录页面加上记住检验框,用户勾选记住登录后,实质上是添加了一个本地的cookie,关闭浏览器后再访问就不用进行重新登录:
<p><input type="checkbox" name="rememberMe"/>记住我</p>
修改shiroconfig
在shiroconfig类中添加rememberMeManager对象,再讲cookie管理对象注入到securityManager中,
public SimpleCookie rememberMeCookie(){
System.out.println("ShiroConfig +rememberMeCookie()" );
// 设置cookie名称,对应login.html页面的<input type="checkbox" name="rememberMe"/>
SimpleCookie cookie = new SimpleCookie("rememberMe");
// cookie.setName("shiroCookie");
cookie.setMaxAge(86400);
return cookie;
}
public CookieRememberMeManager rememberMeManager(){
System.out.println("ShiroConfig +rememberMeManager()" );
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
return cookieRememberMeManager;
}
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm());
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
修改权限配置
修改shiroFilterFactoryBean中的权限过滤,修改authc为user,即用户通过认证或勾选了rememberMe之后记住用户的信息,直接可以访问,主要修改以下两项,用户可以直接index页面
//配置记住我事调用 user
filterChainDefinitionMap.put("/index","user");
filterChainDefinitionMap.put("/","user");
最后在登录页面添加
<input type="checkbox" name="rememberMe"/>记住我
和修改对应的logincontroller即可实现。
参考: