本文将利用springboot集成shiro进行用户的登陆验证功能的开发实现。
springboot集成shiro需要引入以下依赖
<!-- shiro-spring -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
实现登陆验证功能之前,我们需要先准备一些数据。
@Getter
@Setter
@ToString
public class User implements Serializable {
private static final long serialVersionUID = -444037253400871944L;
/** 用户编码 */
private Integer id;
/** 用户名 */
private String userName;
/** 账户密码 */
private String password;
/** 创建日期 */
private Date createTime;
/** 账户状态 1正常 0锁定 */
private String status;
}
数据准备好之后,就可以进行登陆验证功能的开发了。(数据操作层省略,具体可以下载代码进行查阅)
shiro进行登陆验证的过程大致可以归纳为以下几点
-
在ShiroConfig中配置SecurityManagerBean,SecurityManager为shiro的安全管理器,subject由他统一管理。具体概念可以参考
https://blog.csdn.net/badguy_gao/article/details/87075726
- 在ShiroConfig中配置ShiroFilterFactoryBean,他是Shiro过滤器工厂类,依赖SecurityManager。
-
根据自己的需求,自定义Realm。
Realm包含
doGetAuthorizationInfo()
和
doGetAuthenticationInfo()
方法 。分别为登陆认证和权限认证。
ShiroConfig
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 设置securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("index");
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 静态文件,设置不拦截
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/fonts/**", "anon");
filterChainDefinitionMap.put("/images/**", "anon");
filterChainDefinitionMap.put("/sass/**", "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(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
// shiro 生命周期处理器
return new LifecycleBeanPostProcessor();
}
/**
* 该Realm需要用户自定义
* @return
*/
@Bean
public ShiroRealm shiroRealm(){
// 配置Realm
ShiroRealm shiroRealm = new ShiroRealm();
return shiroRealm;
}
}
过滤器filterChain是基于短路机制实现的(最先匹配原则)。
anon,authc,logout等。是shiro为我们实现的过滤器。
anon
org.apache.shiro.web.filter.authc.AnonymousFilter 匿名拦截器,即不需要登录即可访问;一般用于静态资源过滤;示例
|
authc
org.apache.shiro.web.filter.authc.FormAuthenticationFilter
基于表单的拦截器;如
/**=authc
,如果没有登录会跳到相应的登录页面登录
logout
org.apache.shiro.web.filter.authc.LogoutFilter
退出拦截器,主要属性:redirectUrl:退出成功后重定向的地址(/),示例
/logout=logout
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端口,其他路径/参数等都一样
roles
org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
角色授权拦截器,验证用户是否拥有所有角色;示例
/admin/**=roles[admin]
user
org.apache.shiro.web.filter.authc.UserFilter
用户拦截器,用户已经身份验证/记住我登录的都可;示例
/**=user
ShiroConfig配置完成之后,我们根据需求实现Realm,然后将其注入到SecurityManager中。
Realm
自定义Realm需要集成AuthorizingRealm类,然后重写 doGetAuthorizationInfo()和doGetAuthenticationInfo()方法即可。 这一节我们只实现登陆验证功能,所以之重写
doGetAuthorizationInfo ()即可。
@Slf4j
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private UserMapper userDao;
/**
* 获取用户角色和权限(暂不实现)
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/**
* 登陆认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 获取界面中输入的用户名和密码
String username = (String) authenticationToken.getPrincipal();
String password = new String((char[]) authenticationToken.getCredentials());
log.info("认证登录:username is {}", username);
// 通过用户名获取用户信息
User user = userDao.getByName(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;
}
}
页面准备:
编写测试页面login.html,index.html
编写LoginController
@Controller
public class LoginController {
/**
* 登陆
* @return
*/
@GetMapping("/login")
public String login(){
return "login";
}
@PostMapping("/login")
@ResponseBody
public Result login(String username, String password, Boolean rememberMe) {
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
return Result.ok();
} catch (UnknownAccountException e) {
return Result.error(e.getMessage());
} catch (IncorrectCredentialsException e) {
return Result.error(e.getMessage());
} catch (LockedAccountException e) {
return Result.error(e.getMessage());
} catch (AuthenticationException e) {
return Result.error("认证失败!");
}
}
@RequestMapping("/")
public String redirectIndex() {
return "redirect:/index";
}
@RequestMapping("/index")
public String index(Model model) {
User user = (User) SecurityUtils.getSubject().getPrincipal();
model.addAttribute("user", user);
return "index";
}
}
登录成功后,根据之前在ShiroConfig中的配置
shiroFilterFactoryBean.setSuccessUrl("/index")
,页面会自动访问/index路径。
接下来,就可以欣赏自己的成果了。http://localhost:8080
测试用户:
正常用户 admin/admin 锁定用户test/test
RememberMe功能
当用户成功登录之后,关闭浏览器然后再次访问该网址时,页面会再次跳转到登陆页面。之前的登陆已经失效。
下面我们开发一个Remember Me功能(Shiro为我们提供了Remember功能)。使用户的登陆状态不会因为浏览器的关闭而失效。
首先在ShiroConfig中添加cookie对象
/**
* cookie对象
* @return
*/
public SimpleCookie rememberMeCookie() {
// 设置cookie名称,对应login.html页面的<input type="checkbox" name="rememberMe"/>
SimpleCookie cookie = new SimpleCookie("rememberMe");
// 设置cookie的过期时间,单位为秒,这里为一小时
cookie.setMaxAge(3600);
return cookie;
}
/**
* cookie管理对象
* @return
*/
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
// rememberMe cookie加密的密钥
cookieRememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
return cookieRememberMeManager;
}
接下来将cookie对象设置到SecurityManager中
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm());
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
将ShiroFilterFactoryBean的
filterChainDefinitionMap.put("/**", "authc");
更改为
filterChainDefinitionMap.put("/**", "user");
user的功能上面有详细介绍。
功能开发完成之后,我们分别在html页面和LoginController中加入RememberMe的相关代码即可。
@PostMapping("/login")
@ResponseBody
public Result login(String username, String password, Boolean rememberMe) {
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
return Result.ok();
} catch (UnknownAccountException e) {
return Result.error(e.getMessage());
} catch (IncorrectCredentialsException e) {
return Result.error(e.getMessage());
} catch (LockedAccountException e) {
return Result.error(e.getMessage());
} catch (AuthenticationException e) {
return Result.error("认证失败!");
}
}
<p><input type="checkbox" name="rememberMe" />记住我</p>
<script th:inline="javascript">
var ctx = [[@{/}]];
function login() {
var username = $("input[name='username']").val();
var password = $("input[name='password']").val();
var rememberMe = $("input[name='rememberMe']").is(':checked');
$.ajax({
type: "post",
url: ctx + "login",
data: {"username": username,"password": password,"rememberMe": rememberMe},
dataType: "json",
success: function (r) {
if (r.code == 0) {
location.href = ctx + 'index';
} else {
alert(r.msg);
}
}
});
}
</script>
当rememberMe参数为true的时候,Shiro就会帮我们记住用户的登录状态。启动项目即可看到效果。