每一个程序员除了最开始的HelloWorld,在开始接触做管理系统后想必最早要先学会的就是登录功能了,shiro框架就是一个可以帮助我们进行登录认证和权限分配校验的框架,这也是在工作了之后接触到有用到这个框架的项目后才打算自己应用一波,因此我也是初学者,用了一下午好不容易完成了登录认证,庆祝一波,入门了!
下面是我完成登录认证功能所做的工作。
一、要添加的maven依赖
首先,我新建了一个springboot项目,然后引入shiro主要的依赖,如下:
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
二、shiro所需类
我们要自己使用shiro框架需要两个类,一个是ShiroConfig配置类,还需要一个自定义的CustomRealm类。
我的ShiroConfig配置如下:
shiro可以配置路径拦截,对不要登录认证的路径放行,需要的路径进行拦截,登录后才能访问。
package com.zhang.demo.shiro;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* [shiro配置类] <br>
*
* @author [zhanghuyang]<br>
* @version 1.0.0<br>
* @date 2020-5-14
*/
@Configuration
public class ShiroConfig {
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//未登录跳转页面
shiroFilterFactoryBean.setLoginUrl("/login");
//没有权限跳转页面
shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
//允许匿名访问
filterChainDefinitionMap.put("/login.do", "anon");
//swagger接口权限 开放
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/v2/**", "anon");
filterChainDefinitionMap.put("/swagger-resources/**", "anon");
//需要登录认证
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
defaultSecurityManager.setRealm(customRealm());
return defaultSecurityManager;
}
/**
* 引入自定义Realm类
*
* @return
*/
@Bean
public CustomRealm customRealm() {
CustomRealm customRealm = new CustomRealm();
return customRealm;
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions)
*
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
/**
* 开启aop注解支持
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
自定义CustomRealm类如下:
该类需要实现AuthorizingRealm后重写两个方法,完成登录认证和权限分配一系列操作。
AuthenticationInfo 是用来存储登录验证的信息,而AuthorizationInfo 可以用来存放登录账号角色权限的信息,我刚开始也是搞混了,毕竟两者就差一个字母。
package com.zhang.demo.shiro;
import com.zhang.demo.service.LoginService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
/**
* [自定义realm类] <br>
*
* @author [zhanghuyang]<br>
* @version 1.0.0<br>
* @CreateDate
*/
public class CustomRealm extends AuthorizingRealm {
@Autowired
private LoginService loginService;
/**
* 获取权限相关信息
*
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//角色
info.addRoles(new ArrayList<>());
//权限
info.addStringPermissions(new ArrayList<>());
return info;
}
/**
* 获取认证的信息
*
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("------登录认证方法------");
String userName = (String) token.getPrincipal();
String passWord = String.valueOf((char[]) token.getCredentials());
if (null == userName) {
throw new AccountException("用户名不正确");
} else if (!loginService.login(userName, passWord)) {
throw new AccountException("密码不正确");
}
return new SimpleAuthenticationInfo(userName, passWord, getName());
}
}
三、登录接口 LoginController
package com.zhang.demo.controller.login;
import com.zhang.demo.common.CodeMessageEnum;
import com.zhang.demo.service.LoginService;
import com.zhang.demo.util.BuildResponseUtils;
import com.zhang.demo.vo.ResponseVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.io.Serializable;
/**
* [登录接口层] <br>
*
* @author zhanghuyang<br>
* @version 1.0.0<br>
* @date 2020/5/14
*/
@Api(tags = "登录")
@RestController
@Slf4j
@RequestMapping(value = "")
public class LoginController {
@Autowired
private LoginService loginService;
/**
* 登录
*
* @param userName
* @param passWord
* @return
*/
@ApiOperation(value = "登录")
@PostMapping("/login.do")
@ApiImplicitParams({
@ApiImplicitParam(name = "userName", value = "用户名", required = true),
@ApiImplicitParam(name = "passWord", value = "密码", required = true)
})
public ResponseVo login(@RequestParam String userName, @RequestParam String passWord) {
ResponseVo responseVo = new ResponseVo();
try {
//创建一个subject
Subject subject = SecurityUtils.getSubject();
//认证前准备token(令牌)
UsernamePasswordToken token = new UsernamePasswordToken(userName, passWord);
//执行认证登录
try {
subject.login(token);
} catch (UnknownAccountException uae) {
BuildResponseUtils.success(responseVo, CodeMessageEnum.LOGIN_USER_NOT_FOUND);
return responseVo;
} catch (IncorrectCredentialsException ice) {
BuildResponseUtils.success(responseVo, CodeMessageEnum.LOGIN_PASSWORD_ERROR);
return responseVo;
} catch (LockedAccountException lae) {
BuildResponseUtils.success(responseVo, CodeMessageEnum.LOGIN_USER_ACCESS_ERROR);
return responseVo;
} catch (ExcessiveAttemptsException eae) {
BuildResponseUtils.success(responseVo, CodeMessageEnum.LOGIN_TOO_MANY_TIMES);
return responseVo;
} catch (AuthenticationException ae) {
BuildResponseUtils.success(responseVo, CodeMessageEnum.LOGIN_PASSWORD_ERROR);
return responseVo;
}
if (subject.isAuthenticated()) {
//若认证通过后,将Sessionid返回
Serializable id = subject.getSession().getId();
String sessionId = String.valueOf(id);
BuildResponseUtils.singleObjectResponse(responseVo, sessionId);
} else {
//若认证失败
token.clear();
BuildResponseUtils.success(responseVo, CodeMessageEnum.LOGIN_FAIL);
}
} catch (Exception e) {
log.error("login error...", e);
BuildResponseUtils.error(responseVo);
}
return responseVo;
}
@ApiOperation("用户登出")
@PostMapping(value = "/logout.do")
public ResponseVo logout() {
ResponseVo responseVo = new ResponseVo();
try {
//获取subject执行登出
Subject subject = SecurityUtils.getSubject();
subject.logout();
log.info("该用户登出");
BuildResponseUtils.success(responseVo);
} catch (Exception e) {
log.error("logout error", e);
BuildResponseUtils.error(responseVo);
}
return responseVo;
}
}
我是用自己定义的返回对象方便返回数据的处理,另外用了Swagger2方便自己测试。
shiro也是一个可以用来做权限校验的框架,因此这也是我想要学习的主要原因,因为管理系统基本都要有关于菜单权限的功能才算搭建起基本框架,做完后更新//TODO.
业精于勤而荒于嬉,行成于思而毁于随 – 韩愈