注:本文为学习时记录的笔记,内容尚浅,后续有时间可能会完善
前言
什么是Shiro?
•Apache Shiro是一个Java 的安全(权限)框架。
•Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。
•Shiro可以完成,认证,授权,加密,会话管理,Web集成,缓存等.
•官网: shiro.apache.org/
•官方文档十分钟快速入门:shiro.apache.org/10-minute-t…
•下载地址:github.com/apache/shir…
Shiro的三大功能
Shiro有三大核心组件,即Subject、SecurityManager 和 Realm
•Subject: 为认证主体。应用代码直接交互的对象是Subject,Subject代表了当前的用户。包含Principals和Credentials两个信息。
•SecurityManager:为安全管理员。是Shiro架构的核心。与Subject的所有交互都会委托给SecurityManager, Subject相当于是一个门面,而SecurityManager才是真正的执行者。它负责与Shiro 的其他组件进行交互。
•Realm:是一个域。充当了Shiro与应用安全数据间的“桥梁”。Shiro从Realm中获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm中获取相应的用户进行比较,来确定用户的身份是否合法;也需要从Realm得到用户相应的角色、权限,进行验证用户的操作是否能过进行,可以把Realm看成DataSource,即安全数据源。
基本使用
我们这里实现使用shiro完成JWT令牌用户身份校验
导入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.28</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
自定义过滤器Realm,里面实现校验逻辑
AuthorizingRealm是 Shiro 中用于处理授权的基础类,我们这里必须继承AuthorizingRealm
public class JwtRealm extends AuthorizingRealm {
private Logger logger = LoggerFactory.getLogger(JwtRealm.class);
private static JwtUtil jwtUtil = new JwtUtil();
// 覆盖了 supports 方法,用于判断传入的 AuthenticationToken 是否为 JwtToken 类型。
// 这是为了确保仅处理 JwtToken 类型的身份验证请求
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JwtToken;
}
// 这个方法用于处理授权信息
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 暂时不需要实现
return null;
}
// 这是 AuthorizingRealm 中的一个关键方法,用于处理身份验证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String jwt = (String) token.getPrincipal();
if (jwt == null) {
throw new NullPointerException("jwtToken 不允许为空");
}
// 判断
if (!jwtUtil.isVerify(jwt)) {
throw new UnknownAccountException();
}
// 可以获取username信息,并做一些处理
String username = (String) jwtUtil.decode(jwt).get("username");
logger.info("鉴权用户 username:{}", username);
return new SimpleAuthenticationInfo(jwt, jwt, "JwtRealm");
}
}
自定义Filter,用于拦截携带Token的请求
public class JwtFilter extends AccessControlFilter {
private Logger logger = LoggerFactory.getLogger(JwtFilter.class);
/**
* isAccessAllowed 判断是否携带有效的 JwtToken
* 所以这里直接返回一个 false,让它走 onAccessDenied 方法流程
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return false;
}
/**
* 返回结果为true表明登录通过
*/
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
HttpServletRequest request = (HttpServletRequest) servletRequest;
// 如果你设定的 token 放到 header 中,则可以这样获取;request.getHeader("Authorization");
JwtToken jwtToken = new JwtToken(request.getParameter("token"));
try {
// 鉴权认证
getSubject(servletRequest, servletResponse).login(jwtToken);
return true;
} catch (Exception e) {
logger.error("鉴权认证失败", e);
onLoginFail(servletResponse);
return false;
}
}
/**
* 鉴权认证失败时默认返回 401 状态码
*/
private void onLoginFail(ServletResponse response) throws IOException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpResponse.getWriter().write("Auth Err!");
}
}
自定义JwtToken
public class JwtToken implements AuthenticationToken {
private String jwt;
public JwtToken(String jwt) {
this.jwt = jwt;
}
/**
* 等同于账户
*/
@Override
public Object getPrincipal() {
return jwt;
}
/**
* 等同于密码
*/
@Override
public Object getCredentials() {
return jwt;
}
}
创建shiro配置类ShiroConfig
这里面声明了四个bean
@Configuration
public class ShiroConfig {
@Bean
public SubjectFactory subjectFactory() {
class JwtDefaultSubjectFactory extends DefaultWebSubjectFactory{
@Override
public Subject createSubject(SubjectContext context) {
// 禁用了session的创建,使Shiro不会创建会话
context.setSessionCreationEnabled(false);
return super.createSubject(context);
}
}
return new JwtDefaultSubjectFactory();
}
// 这里使用了自定义的 JwtRealm 来处理基于 JWT(JSON Web Token)的身份验证
@Bean
public Realm realm() {
return new JwtRealm();
}
@Bean
public DefaultWebSecurityManager securityManager() {
// DefaultWebSecurityManager,它是 Shiro 的安全管理器
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm());
// 关闭 ShiroDAO 功能
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
// 不需要将 Shiro Session 中的东西存到任何地方(包括 Http Session 中)
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
securityManager.setSubjectDAO(subjectDAO);
// 禁止Subject的getSession方法
securityManager.setSubjectFactory(subjectFactory());
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean() {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager());
shiroFilter.setLoginUrl("/unauthenticated");
shiroFilter.setUnauthorizedUrl("/unauthorized");
// 添加jwt过滤器
Map<String, Filter> filterMap = new HashMap<>();
// 设置过滤器【anon\logout可以不设置】
filterMap.put("anon", new AnonymousFilter());
filterMap.put("jwt", new JwtFilter()); //使用自定义的Jwt过滤器
filterMap.put("logout", new LogoutFilter());
shiroFilter.setFilters(filterMap);
// 拦截器,指定方法走哪个拦截器 【login->anon】【logout->logout】【verify->jwt】
Map<String, String> filterRuleMap = new LinkedHashMap<>();
filterRuleMap.put("/login", "anon");
filterRuleMap.put("/logout", "logout");
filterRuleMap.put("/verify", "jwt");
shiroFilter.setFilterChainDefinitionMap(filterRuleMap);
return shiroFilter;
}
}
编写controller方法,测试校验
@RestController
public class TestCOntroller {
/**
* http://localhost:8080/authorize?username=xfg&password=123
* 身份校验获取token
*/
@RequestMapping("/authorize")
public ResponseEntity<Map<String, String>> authorize(String username, String password) {
Map<String, String> map = new HashMap<>();
// 模拟账号和密码校验
if (!"xfg".equals(username) || !"123".equals(password)) {
map.put("msg", "用户名密码错误");
return ResponseEntity.ok(map);
}
// 校验通过生成token
JwtUtil jwtUtil = new JwtUtil();
Map<String, Object> chaim = new HashMap<>();
chaim.put("username", username);
String jwtToken = jwtUtil.encode(username, 60 * 60 * 1000, chaim);
map.put("msg", "授权成功");
map.put("token", jwtToken);
// 返回token码
return ResponseEntity.ok(map);
}
// 首先会走我们设置的/verify对应的拦截器,校验通过才会进入下面
@GetMapping("/verify")
public String success(){
return "verify";
}
}
/verify为shiroFilterFactoryBean中定义的拦截器路径
当访问该接口时,无效token则会校验失败
访问 http://localhost:8080/authorize?username=xfg&password=123 获取token
使用获取到的token访问,验证通过