接续上一篇,spring boot 、mybatis-plus、shiro整合入门教程(二)——mybatis-plus常用操作
这篇主要介绍下shiro在项目中是如何使用的,
前期的主要工作是数据库表的设计,上篇文章已经给出表的设计,详细的数据库初始化sql,可以参考代码库的sql,
简单说下登录原理,用户每次登录的时候生成token,同时根据用户id更新数据库中的token有效时间和token值,用户下次登录的时候会去校验token的有效时间,不在有效期内会返回登录页重新登录刷新。
登录的代码和shiro整合的相关代码:
/**
* 登录
* @param username
* @param password
* @param captcha
* @param randomStr
* @return
*/
@RequestMapping (value="/login", method = RequestMethod.POST)
public Map<String, Object> login(String username, String password,String captcha, String randomStr) {
String value = (String)redisTemplate.opsForValue().get(Constants.NUMBER_CODE_KEY + randomStr);
if (StringUtils.isBlank(value)) {
return WebResult.error("验证码过期");
}
if (!captcha.equals(value)) {
return WebResult.error("验证码不正确");
}
SysUser sysUser = sysUserService.getOne(username);
if(sysUser == null || !sysUser.getPassword().equals(new Sha256Hash(password, sysUser.getSalt()).toHex())) {
return WebResult.error("账号或密码不正确");
}
if(sysUser.getStatus() == 0){
return WebResult.error("账号已被锁定,请联系管理员");
}
//生成token 保存到数据库 可优化为存储到redis和数据库
return sysUserTokenService.createToken(sysUser.getUserId());
}
/**
* 生成token
* @param userId
* @return
*/
public WebResult createToken(long userId) {
String token = TokenGenerator.generateValue();
//当前时间
LocalDateTime now = LocalDateTime.now();
//过期时间
LocalDateTime expireTime = now.plusHours(Constants.EXPIRE);
//判断是否生成token
SysUserToken sysUserToken = sysUserTokenMapper.selectById(userId);
if(sysUserToken == null){
sysUserToken = new SysUserToken();
sysUserToken.setUserId(userId);
sysUserToken.setToken(token);
sysUserToken.setUpdateTime(now);
sysUserToken.setExpireTime(expireTime);
baseMapper.insert(sysUserToken);
}else{
sysUserToken.setToken(token);
sysUserToken.setUpdateTime(now);
sysUserToken.setExpireTime(expireTime);
sysUserTokenMapper.updateById(sysUserToken);
}
return new WebResult().put("token", token).put("expire", expireTime);
}
项目中使用shiro需要继承AuthenticatingFilter和AuthorizingRealm,代码如下:
/**
* @author liuyi
* @date 2019/5/15
*
* oauth 过滤器
*/
@Slf4j
public class OAuthFilter extends AuthenticatingFilter {
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
String token = getRequestToken((HttpServletRequest) request);
if(StringUtils.isBlank(token)){
return null;
}
return new OAuthToken(token);
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (request instanceof HttpServletRequest) {
if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")) {
return true;
}
}
return super.isAccessAllowed(request, response, mappedValue);
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token,如果token不存在,直接返回401
String token = getRequestToken((HttpServletRequest) request);
if(StringUtils.isBlank(token)){
HttpServletResponse httpResponse = (HttpServletResponse) response;
String json = JSON.toJSONString(WebResult.error(HttpStatus.SC_UNAUTHORIZED, "invalid token"));
httpResponse.getWriter().print(json);
return false;
}
return executeLogin(request, response);
}
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException ep, ServletRequest request, ServletResponse response) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setContentType("application/json;charset=utf-8");
try {
//处理登录失败的异常
Throwable throwable = ep.getCause() == null ? ep : ep.getCause();
WebResult result = WebResult.error(HttpStatus.SC_UNAUTHORIZED, throwable.getMessage());
String json = JSON.toJSONString(result);
httpResponse.getWriter().print(json);
} catch (IOException e) {
log.error("OAuthFilter 异常", e);
}
return false;
}
/**
* 获取请求的token
* @param httpRequest
* @return
*/
private String getRequestToken(HttpServletRequest httpRequest){
//从header中获取token
String token = httpRequest.getHeader("token");
//如果header中不存在token,则从参数中获取token
if(StringUtils.isBlank(token)){
token = httpRequest.getParameter("token");
}
return token;
}
}
@Component
public class OAuthRealm extends AuthorizingRealm {
@Resource
private ShiroServiceImpl shiroService;
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof OAuthToken;
}
/**
* 授权(验证权限时调用)
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SysUser user = (SysUser)principals.getPrimaryPrincipal();
Long userId = user.getUserId();
//用户权限列表
Set<String> permsSet = shiroService.getUserPermissions(userId);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(permsSet);
return info;
}
/**
* 认证
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String accessToken = (String) token.getPrincipal();
//根据accessToken,查询用户信息
SysUserToken tokenEntity = shiroService.queryByToken(accessToken);
//token失效
if(tokenEntity == null || tokenEntity.getExpireTime().toInstant(ZoneOffset.of("+8")).toEpochMilli() < System.currentTimeMillis()){
throw new IncorrectCredentialsException("token失效,请重新登录");
}
//查询用户信息
SysUser user = shiroService.queryUser(tokenEntity.getUserId());
//账号锁定
if(user.getStatus() == 0){
throw new LockedAccountException("账号已被锁定,请联系管理员");
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, accessToken, getName());
return info;
}
}
public class OAuthToken implements AuthenticationToken {
private String token;
public OAuthToken(String token) {
this.token = token;
}
@Override
public String getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
/**
* @author liuyi
* @date 2019/5/15
*/
@Configuration
public class ShiroConfig {
/**
* 权限过滤器
* @param securityManager
* @return
*/
@Bean(name="shiroFilter")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
shiroFilter.setLoginUrl("/sys/unauthorized");
shiroFilter.setUnauthorizedUrl("/sys/unauthorized");
//oauth过滤
Map<String, Filter> filters = new HashMap<>();
filters.put("oauth", new OAuthFilter());
shiroFilter.setFilters(filters);
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/actuator/**", "anon");
//APP 模块开放 后面通过拦截器管理
filterMap.put("/app/**", "anon");
//用户密码登录
filterMap.put("/sys/login", "anon");
//未认证
filterMap.put("/sys/unauthorized", "anon");
//验证码
filterMap.put("/sys/captcha/**", "anon");
filterMap.put("/v2/**", "anon");
filterMap.put("/", "anon");
filterMap.put("/**", "oauth");
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
/**
* 配置安全事务管理器
* @param authRealm
* @param sessionManager
* @return
*/
@Bean("securityManager")
public SecurityManager securityManager(OAuthRealm authRealm, SessionManager sessionManager) {
DefaultWebSecurityManager manager=new DefaultWebSecurityManager();
manager.setRealm(authRealm);
manager.setSessionManager(sessionManager);
return manager;
}
/**
* session管理
* @return
*/
@Bean("sessionManager")
public SessionManager sessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setSessionIdCookieEnabled(true);
return sessionManager;
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
return new LifecycleBeanPostProcessor();
}
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator creator=new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor=new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
/**
* @author liuyi
* @date 2019/5/15
*/
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean shiroFilterRegistration() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new DelegatingFilterProxy("shiroFilter"));
registrationBean.addInitParameter("targetFilterLifecycle", "true");
registrationBean.setEnabled(true);
registrationBean.setOrder(Integer.MAX_VALUE - 1);
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
@Bean
public FilterRegistrationBean xssFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setFilter(new XssFilter());
registration.addUrlPatterns("/*");
registration.setName("xssFilter");
registration.setOrder(Integer.MAX_VALUE);
return registration;
}
}