写给我大聂哥
其他的内容在这里
shiro框架的密码校验(二)
shiro框架的权限设定(三)
shiro框架的密码校验进阶(四)多登陆方式实现思路
1.什么是shiro
官方说法:Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.
Apache Shiro™是一个强大且易用的Java安全框架,能够用于身份验证、授权、加密和会话管理。Shiro拥有易于理解的API,您可以快速、轻松地获得任何应用程序——从最小的移动应用程序到最大的网络和企业应用程序。
简单来说就是:Shiro是apache的一个开源安全框架,作用就是实现用户身份认证、权限授权、加密、会话管理等功能。
2.shiro有那些模块
2.1主要模块
- Authentication:认证/登录
认证简单来说就是登录,判断用户的账号密码是否正确,有没有这个用户。 - Authorization:授权与权限验证。
授权就是给与用户某个权限,权限验证就是判断这个用户有没有权限干这个事 。
例如:你打LOL选了瞎子,你能看到所有皮肤,但是你没买的你用不了,为什么呢?
因为你没这个权限呗,你买了我就给你加上这个权限,你就能用了。 - SessionManagement:会话管理
会话就是说用户访问应用时保持的连接关系,能够识别出当前访问的用户是谁。 - Cryptography:加密
加密就是加密,字面意思,使用密码算法保持数据安全,同时仍然易于使用。
2.2次要模块
官方说法
- Web支持:Shiro的Web支持API可帮助轻松保护Web应用程序。
- 缓存:缓存是Apache Shiro API的第一层公民,可确保安全操作保持快速有效。
- 并发性:Apache Shiro的并发功能支持多线程应用程序。
- 测试:测试支持可帮助您编写单元测试和集成测试,并确保您的代码将按预期进行保护。
- “运行方式”:一种功能,允许用户采用其他用户的身份(如果允许),有时在管理方案中很有用。
- “记住我”:在整个会话中记住用户的身份,因此他们只需要在必要时登录。
注意: Shiro不会去维护用户、维护权限,这些需要我们自己去设计/提供,然后通过相应的接口注入给Shiro
3.shiro高层概念
3.1三个主要概念
- Subject
代表当前用户,用户的抽象概念,它会被绑定到SecurityManager,所有的交互都由SecurityManager来完成
简单来说相当于一个实体类,它本身没有功能性,只是起到一个存储数据的作用。 - SecurityManager
SecurityManager是shiro的核心,所有的安全交互都在这个里面完成。 - Realm
Realm是一个域,SecurityManager在Realm中获取安全数据,如用户是否合法,是否有权限访问
也就是说上面描述的两个模块Authentication和Authorization都是在这个概念里面。
3.2从上面这个概念描述我们可以看出运行的顺序
用户登录->Subject->SecurityManager->Realm进行验证。而Realm我们上面也说了Authentication和Authorization都是在这个概念里面,那么这两个模块先运行谁呢?
那肯定是Authentication登录认证啊,你不登录去查Authorization权限干嘛咧?看有没有权限登录?你搁这想屁吃呢。
4.shiro实现登录
4.1 maven配置
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!-- MySQL 连接驱动依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!-- spring boot 热部署 stat-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- spring boot 热部署 end -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
</configuration>
</plugin>
</plugins>
</build>
4.2 JacksonUtil工具类
package com.lzl.practice.Util;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.List;
public class JacksonUtil {
public static String parseString(String body, String field) {
ObjectMapper mapper = new ObjectMapper();
JsonNode node = null;
try {
node = mapper.readTree(body);
JsonNode leaf = node.get(field);
if (leaf != null)
return leaf.asText();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static List<String> parseStringList(String body, String field) {
ObjectMapper mapper = new ObjectMapper();
JsonNode node = null;
try {
node = mapper.readTree(body);
JsonNode leaf = node.get(field);
if (leaf != null)
return mapper.convertValue(leaf, new TypeReference<List<String>>() {
});
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static Integer parseInteger(String body, String field) {
ObjectMapper mapper = new ObjectMapper();
JsonNode node = null;
try {
node = mapper.readTree(body);
JsonNode leaf = node.get(field);
if (leaf != null)
return leaf.asInt();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static List<Integer> parseIntegerList(String body, String field) {
ObjectMapper mapper = new ObjectMapper();
JsonNode node = null;
try {
node = mapper.readTree(body);
JsonNode leaf = node.get(field);
if (leaf != null)
return mapper.convertValue(leaf, new TypeReference<List<Integer>>() {
});
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static Boolean parseBoolean(String body, String field) {
ObjectMapper mapper = new ObjectMapper();
JsonNode node = null;
try {
node = mapper.readTree(body);
JsonNode leaf = node.get(field);
if (leaf != null)
return leaf.asBoolean();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static Short parseShort(String body, String field) {
ObjectMapper mapper = new ObjectMapper();
JsonNode node = null;
try {
node = mapper.readTree(body);
JsonNode leaf = node.get(field);
if (leaf != null) {
Integer value = leaf.asInt();
return value.shortValue();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static Byte parseByte(String body, String field) {
ObjectMapper mapper = new ObjectMapper();
JsonNode node = null;
try {
node = mapper.readTree(body);
JsonNode leaf = node.get(field);
if (leaf != null) {
Integer value = leaf.asInt();
return value.byteValue();
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static <T> T parseObject(String body, String field, Class<T> clazz) {
ObjectMapper mapper = new ObjectMapper();
JsonNode node = null;
try {
node = mapper.readTree(body);
node = node.get(field);
return mapper.treeToValue(node, clazz);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static Object toNode(String json) {
if (json == null) {
return null;
}
ObjectMapper mapper = new ObjectMapper();
try {
JsonNode jsonNode = mapper.readTree(json);
return jsonNode;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
4.3 配置shiroConfig
@Configuration
public class ShiroConfig {
//将自己的登录验证方式加入容器
//直接写return new AuthRealm()也是一样的,但是不要在下面的securityManager()方法中直接new AuthRealm()
//直接new的话我只能说好家伙,自己实例化对象之后该类就不会在springboot容器中了,
//这样就不能在AuthRealm类中autowired了
@Bean
public Realm AuthRealm() {
AuthRealm authRealm = new AuthRealm();
return authRealm;
}
//将自己配置的AuthRealm配置到securityManager中
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(AuthRealm());
return securityManager;
}
//Filter工厂,设置对应的过滤条件和跳转条件
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String,String> map = new HashMap<String, String>();
//登出
map.put("/logout","logout");
//对所有用户认证
map.put("/**","authc");
//首页
shiroFilterFactoryBean.setSuccessUrl("/index");
//错误页面,认证不通过跳转
shiroFilterFactoryBean.setUnauthorizedUrl("/auth/error");
shiroFilterFactoryBean.setLoginUrl("/auth/login");//登录
shiroFilterFactoryBean.setSuccessUrl("/auth/index");
shiroFilterFactoryBean.setUnauthorizedUrl("/auth/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
//开启代码支持
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
shirFilter工厂里面的的logout、authc、anon都是拦截器。
拦截器链名定义
名字 | 名称 | 说明 |
---|---|---|
anon | 匿名拦截器 | 不需要登录就能访问,一般用于静态资源,或者移动端接口 |
authc | 登录拦截器 | 需要登录认证才能访问的资源 |
user | 用户拦截器 | 用户拦截器,用户已经身份验证 / 记住我登录的都可; |
logout | 登出拦截器 | 用户登出拦截器 |
4.4 配置AuthRealm
public class AuthRealm extends AuthorizingRealm {
/***
* @Description: 这块是授权配置
* @Param: [principalCollection]
* @return: org.apache.shiro.authz.AuthorizationInfo
* @Author: lizelin
* @Date: 2021/5/13 0013 1:25
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
return info;
}
/***
* @Description: 这块是登录验证配置
* @Param: [token]
* @return: org.apache.shiro.authc.AuthenticationInfo
* @Author: lizelin
* @Date: 2021/5/13 0013 1:25
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//AuthenticationToken转成UsernamePasswordToken这样可以直接拿到用户名和密码
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
//判断用户名是否为aaa否则抛出异常完事密码错误
if(!username.equals("aaa")){
throw new UnknownAccountException("用户错误");
}
return new SimpleAuthenticationInfo(upToken.getUsername(), upToken.getPassword(), "ShiroReaml");
}
这里我只写了登录验证配置,判断用户名是否正确,简单测试一下,加入授权和密码验证明天再说今天困了。
4.5 配置controller测试
@RestController
@RequestMapping("/auth")
public class AuthController {
/**
* @Description: 登录验证
* @Param: [body]
* @return: java.lang.Object
* @Author: Mr.Wang
* @Date: 2021/5/13 0013 1:50
*/
@PostMapping("/login")
public Object login(@RequestBody String body) {
String username = JacksonUtil.parseString(body, "username");
String password = JacksonUtil.parseString(body, "password");
//获取Subject
Subject subject = SecurityUtils.getSubject();
try {
//登录测试,就这一行代码
subject.login(new UsernamePasswordToken(username, password));
} catch (UnknownAccountException uae) {
return "用户帐号或密码不正确";
} catch (LockedAccountException lae) {
return "用户帐号已锁定不可用";
} catch (AuthenticationException ae) {
return "用户帐号已锁定不可用";
}
return "登录成功,账号名:"+username;
}
@PostMapping("/testAuthc")
public Object testAuthc() {
return "testAuthc";
}
@GetMapping("/401")
public Object page401() {
return "401";
}
@GetMapping("/index")
public Object pageIndex() {
return "index";
}
@GetMapping("/403")
public Object page403() {
return "index";
}
@GetMapping("/error")
public Object error() {
return "error";
}
}
简单的测试环境就搭好了,完事我们去测试。
5.spring boot+shiro测试
5.1账号正确测试
ok登录成功,测试没毛病。
5.2账号错误测试
ok返回密码错误,测试没毛病。
5.3拦截器authc测试
ok返回地址是/auth/login,测试拦截没毛病。
简单的登陆用户名验证就完事了,然后写密码验证,权限,三方登录