一、先了解拦截器在http请求中所占的位置
shiro配置文件:
@Configuration
public class ShiroConfig {
//配置类的三大属性
//一、shiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//HashMap<String, Filter> filterHashMap = new HashMap<>(16);
//filterHashMap.put("jwt", new ShiroFilter());
Map<String, Filter> filterMap = new HashMap<>(16);
filterMap.put("jwt", new ShiroFilter());
bean.setFilters(filterMap);
//设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
//添加shiro的内置过滤器
/**
* anon:无需认证就可访问
* authc:必须认证了才能访问
* user:必须拥有,记住我,功能才能使用
* perms:拥有对某个资源的权限才能访问
* role:拥有某个角色权限才能访问
*/
LinkedHashMap<String, String> filterChainMap = new LinkedHashMap<>();
// filterChainMap.put("/user/add", "authc");
// filterChainMap.put("/user/update", "authc");
// 可以使用通配符
// filterChainMap.put("/user/*", "authc");
filterChainMap.put("/base", "anon");
filterChainMap.put("/**", "jwt");
//普通用户权限
filterChainMap.put("/user/user", "perms[per:user]");
//管理员权限
filterChainMap.put("/user/admin", "perms[per:admin]");
bean.setFilterChainDefinitionMap(filterChainMap);
bean.setUnauthorizedUrl("/unauthor");
bean.setLoginUrl("/toLogin");
return bean;
}
//二、DefaultWebSecurityManager
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联realm
securityManager.setRealm(userRealm);
return securityManager;
}
//三、创建realm 对象,需要自定义类
@Bean
public UserRealm userRealm() {
return new UserRealm();
}
}
三、自定义ream
public class UserRealm extends AuthorizingRealm {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private UserRoleService userRoleService;
@Autowired
private UserInfoMapper userInfoMapper;
/**
* Retrieves the AuthorizationInfo for the given principals from the underlying data store. When returning
* an instance from this method, you might want to consider using an instance of
* {@link SimpleAuthorizationInfo SimpleAuthorizationInfo}, as it is suitable in most cases.
*
* @param principals the primary identifying principals of the AuthorizationInfo that should be retrieved.
* @return the AuthorizationInfo associated with this principals.
* @see SimpleAuthorizationInfo
*/
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//授权操作
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Subject subject = SecurityUtils.getSubject();
//User user = (User) subject.getPrincipal();
UserInfo user = (UserInfo) subject.getPrincipal();
UserRole userRolePermission = userRoleService.getUserRolePermission(user.getUserEmail());
//List list = userService.queryUserPermissionsList(user.getUsername());
//subject.getPrincipal();
// 利用user对象,获取user对应的相关权限并加入到授权中
// Iterator iterator = list.iterator();
// while (iterator.hasNext()) {
// String next = (String) iterator.next();
// info.addStringPermission(next);
// }
info.addStringPermission(userRolePermission.getPermission());
info.addStringPermission(userRolePermission.getRole());
return info;
}
/**
* Retrieves authentication data from an implementation-specific datasource (RDBMS, LDAP, etc) for the given
* authentication token.
* <p/>
* For most datasources, this means just 'pulling' authentication data for an associated subject/user and nothing
* more and letting Shiro do the rest. But in some systems, this method could actually perform EIS specific
* log-in logic in addition to just retrieving data - it is up to the Realm implementation.
* <p/>
* A {@code null} return value means that no account could be associated with the specified token.
*
* @param token the authentication token containing the user's principal and credentials.
* @return an {@link AuthenticationInfo} object containing account data resulting from the
* authentication ONLY if the lookup is successful (i.e. account exists and is valid, etc.)
* @throws AuthenticationException if there is an error acquiring data or performing
* realm-specific authentication logic for the specified <tt>token</tt>
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof UsernamePasswordToken;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取当前的用户
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
// 封装用户的登录数据
UserInfo userInfo = userInfoMapper.selectByUserEmail(userToken.getUsername());
String s = Arrays.toString(userToken.getPassword());
if (userInfo == null) {
throw new EventException(HttpStatus.BAD_REQUEST, "用户不存在,请确认账号是否正确!");
} else if (userInfo.getUserPassword().equals(s)) {
logger.info("密码校验出错!");
throw new EventException(HttpStatus.BAD_REQUEST, "密码不匹配,请确认密码正确!");
}
//发送认证信息{principal:要义;credentials:证书;realmName:领域名称}
// new SimpleAuthenticationInfo(Object principal, Object credentials, String realmName)
// 如果要把对应的用户传到授权的环节,就要在principal上放置user
return new SimpleAuthenticationInfo(userInfo, userInfo.getUserPassword(), "");
}
}
四、自定义过滤器
public class ShiroFilter extends BasicHttpAuthenticationFilter {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
//这里只有返回false才会执行onAccessDenied方法,因为
// return super.isAccessAllowed(request, response, mappedValue);
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token
String token = getRequestToken((HttpServletRequest) request);
String login = ((HttpServletRequest) request).getServletPath();
RedisTemplateService redisTemplateService = SpringUtils.getBean(RedisTemplateService.class);
//判断是否是通用的/base请求,不需要拦截
if (StringUtils.isMatch(login)) {
logger.info("请求路径为:" + login + ",不需要拦截");
return true;
}
//没有token
if (StringUtils.isEmpty(token)) {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
AjaxResult ajaxResult = AjaxResult.error(HttpStatus.UNAUTHORIZED, "请先登录后再操作!");
String s = new ObjectMapper().writeValueAsString(ajaxResult);
response.getWriter().print(s);
logger.error("请求路径==:" + login + "没有token");
return false;
}
JWTUtil.verify(token);
//从当前shiro中获得用户信息
String userEmail = JWTUtil.getUserEmail(token);
String userToken = redisTemplateService.get(userEmail);
if (userToken.equals(token)) {
//TODO 判断token是否需要更新,如果需要就更新(视情况而定)
//if (JWTUtil.isNeedUpdate(token)) {
// String updateToken = JWTUtil.updateToken(token);
// redisTemplateService.saveToken(userEmail, updateToken);
//}
logger.info("请求路径==:" + login + "通过过滤");
return true;
} else {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
AjaxResult ajaxResult = AjaxResult.error(HttpStatus.UNAUTHORIZED, "登录已过期,请重新登录!");
String s = new ObjectMapper().writeValueAsString(ajaxResult);
response.getWriter().print(s);
logger.error("请求路径==:" + login + "无效token");
}
return false;
}
private String getRequestToken(HttpServletRequest request) {
//默认从请求头中获得token
return request.getHeader("Token");
}
/**
* Check if a given log record should be published.
*
* @param record a LogRecord
* @return true if the log record should be published.
*/
}
五、引入token的工具类和方法实现
public class JWTUtil {
//设置的一个密钥
private static final String USER_SRCRET = "booksalon";
public static final Date expireTime() {
//创建一个日历
Calendar instance = Calendar.getInstance();
//默认令牌过期时间8小时
instance.add(Calendar.HOUR, 12);
return instance.getTime();
}
public static String updateToken(String update) {
try {
return JWT.create()
.withSubject(update)
.withExpiresAt(expireTime())
.sign(Algorithm.HMAC256(USER_SRCRET));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
/**
* 获取token
*
* @param u user
* @return token
*/
// public static String getToken(UserDetails u) {
// //创建一个日历
// Calendar instance = Calendar.getInstance();
// //默认令牌过期时间8小时
// instance.add(Calendar.HOUR, 1);
//
// //创建JWT并在负载中加入用户id,和电话
// JWTCreator.Builder builder = JWT.create();
// builder.withClaim("id", u.getUsername());
// //.withClaim("phone", u.getAuthorities());
//
// return builder.withExpiresAt(instance.getTime())
// .sign(Algorithm.HMAC256(USER_SRCRET));
//// return builder.sign(Algorithm.HMAC256(USER_SRCRET));
// }
//shiro
public static String getToken(UserInfo u) {
//创建一个日历
Calendar instance = Calendar.getInstance();
//默认令牌过期时间8小时
instance.add(Calendar.HOUR, 12);
//创建JWT并在负载中加入用户邮箱
JWTCreator.Builder builder = JWT.create();
builder.withClaim("userEmail", u.getUserEmail());
try {
return builder.withExpiresAt(instance.getTime())
.sign(Algorithm.HMAC256(USER_SRCRET));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
// return builder.sign(Algorithm.HMAC256(USER_SRCRET));
}
/**
* 验证token合法性 成功返回token
*/
public static DecodedJWT verify(String token) throws Exception {
if (token == null) {
throw new Exception("token不能为空");
}
JWTVerifier build = JWT.require(Algorithm.HMAC256(USER_SRCRET)).build();
return build.verify(token);
}
//获取token中的userEmail
public static String getUserEmail(String token) throws Exception {
DecodedJWT verify = verify(token);
return verify.getClaim("userEmail").asString();
}
/**
* 检查token是否需要更新
*
* @param token
* @return
*/
public static boolean isNeedUpdate(String token) {
//获取token过期时间
Date expiresAt = null;
try {
expiresAt = JWT.require(Algorithm.HMAC256(USER_SRCRET))
.build()
.verify(token)
.getExpiresAt();
} catch (TokenExpiredException e) {
return true;
} catch (Exception e) {
throw new RuntimeException("token验证失败");
}
//如果剩余过期时间少于过期时常的一般时 需要更新
return (expiresAt.getTime() - System.currentTimeMillis()) / 1000 / 60 / 60 < 3;
}
/* public static void main(String[] args) {
DecodedJWT verify = verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTcxMDg1MDAsInVzZXJuYW1lIjoiYWRtaW4ifQ.geBEtpluViRUg66_P7ZisN3I_d4e32Wms8mFoBYM5f0");
System.out.println(verify.getClaim("password").asString());
}*/
}
六、用户接入shiro登录,subject是一个全局可用的对象
Subject subject = SecurityUtils.getSubject();
try {
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userInfo.getUserEmail(),
StringUtils.passwordMd5(userInfo.getUserPassword()));
subject.login(usernamePasswordToken);
} catch (Exception e) {
return AjaxResult.error(e.getMessage());
}
需要引入的包:
<!--shiro 鉴权和授权导包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.1</version>
</dependency>
<!-- jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.3.0</version>
</dependency>