Sping-boot笔记整理
一、swagger2
1. 什么是swagger2
在实际项目开发中往往需要很多人一起合作,每个人都会编写自己设计的类、接口等。其他人不需要了解这个类或方法的具体实现方式直接通过Api来调用这个方法。为了更好的合作我们需要编写一份Api文档来说明每个类、接口、方法的作用。Swagger2可以更好的、快速的帮助我们编写最新的Api文档。
2.导入依赖
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
3.swagger2的使用方法
导入swgger所需的依赖包,然后编写swaggerConfig类(都是固定写法)
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
// 指定构建api文档的详细信息的方法:apiInfo()
.apiInfo(apiInfo())
.select()
// 指定要生成api接口的包路径,这里把controller作为包路径,生成controller中的所有接口
.apis(RequestHandlerSelectors.basePackage("com.zzl.controller"))
.paths(PathSelectors.any())
.build();
}
/**
* 构建api文档的详细信息
* @return
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
// 设置页面标题
.title("Spring Boot集成Swagger2接口总览")
// 设置接口描述
.description("接口说明")
// 设置联系方式
.termsOfServiceUrl("http://localhost:8080/")
// 设置版本
.version("1.0")
// 构建
.build();
}
}
在controller包中添加@Api、@ApiOperation、@ApiParam等注解
@RestController
@RequestMapping("/swagger")
@Api(value = "测试接口", tags = "用户管理相关的接口", description = "用户测试接口")
public class Swagger {
@GetMapping("/get/{id}")
@ApiOperation(value = "根据用户的唯一标识获取用户信息")
public JsonResult<User> getUserInfo(@PathVariable @ApiParam(value="用户唯一标识")Long id){
User user =new User(id,"zzl","123456");
return new JsonResult(user);
}
}
最后在localhost:8080/swagger-ui.html查看效果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5PsDudAH-1648108013450)(C:\Users\25412\AppData\Roaming\Typora\typora-user-images\image-20220319204845516.png)]
4.关于整合swagger2中遇到的bug
在sping-boot中整合swagger2时会遇到一个坑,就是因为spring-boot版本过高(2.6.0以上),现在没有可以可以完美匹配的swagger2版本所以需要将spring-boot的版本回退到2.5.6(注:这个时候pom中会爆红,但是项目可以启动)
二、spring-boot中整合aop
1.回顾aop
aop:Aspect Oriented Programming的缩写,意为面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。相当于一种横向编程,在不改变原有的代码上,实现许多的增强方法,显示日志、打印消息等可以降低代码的耦合度。首先要明白aop中的切面是什么意思,切面就是需要实现横向编程的那个类。然后可以通过动态代理实现前置通知、后置通知、环绕通知、异常抛出通知等
2.导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
3.创建Aop测试类
@Aspect设置该类为切面类
@Pointcut设置切面。该切面为controller包中的所有类、方法等
@Pointcut("execution(* com.zzl.controller..*.*(..))")
public void pointCut() {
}
@Before()前置通知。JoinPoint 中封装了getSingnature等方法
在spring-boot中通过HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();来获取当前Request实例
@Before("pointCut()")
public void doBefore(JoinPoint joinPoint) {
log.info("====doBefore方法进入了====");
Signature signature = joinPoint.getSignature();
String declaringTypeName = signature.getDeclaringTypeName();
String funcName = signature.getName();
log.info("即将执行方法为:{},输入包{}", funcName, declaringTypeName);
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String url = request.getRequestURL().toString();
String ip = request.getRemoteAddr();
log.info("用户请求的url为:{},ip地址为:{}", url, ip);
}
Request实例常用的方法:
getRequestURL方法返回客户端发出请求时的完整URL。
getRequestURI方法返回请求行中的资源名部分。
getQueryString 方法返回请求行中的参数部分。
getPathInfo方法返回请求URL中的额外路径信息。额外路径信息是请求URL中的位于Servlet的路径之后和查询参数之前的内容,它 以“/”开头。
getRemoteAddr方法返回发出请求的客户机的IP地址。
getRemoteHost方法返回发出请求的客户机的完整主机名。
getRemotePort方法返回客户机所使用的网络端口号。
getLocalAddr方法返回WEB服务器的IP地址。
getLocalName方法返回WEB服务器的主机名。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AG5UAmSc-1648108013452)(C:\Users\25412\AppData\Roaming\Typora\typora-user-images\image-20220319221834019.png)]
三、sping-boot中整合mybatis
1.什么是mybatis和mybatis的作用
众说周知Mybaits是一个持久层框架。但什么是持久层:持久层又叫数据访问层(Dao)层,是和数据库直接打交到的层。简单的说就是把CRUD等操作封装成一个独立的层。什么是持久化:狭义上就是指将域对象永久保存到数据库中,广义上指将数据以某种技术保存起来,以后在用(如保存到硬盘中)。
插个知识:
域对象是什么:可以在不同的Servlet中进行数据传递的对象就称为域对象。
域对象必须有的方法如下:
setAttribute(name,value);存储数据的方法
getAttribute(name);根据name获取对应数据值
removeAttribute(name);删除数据
4种域对象:page(jsp中使用现在基本不用)、request(HttpServletRequest域)、session域、ServletContext(aplication域)
问题:Spring-boot中Servlet去哪了
接着为什么要使用持久化:持久化技术封装了数据访问细节,为大部分业务逻辑提供面向对象的API。避免不断的进行访问数据库的操作,可以重复使用
2.导入依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
如果在yml中出现了数据库连接爆红的情况可以通过去除mysql的版本号来解决。具体原因未知
3.配置application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/user1?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: 418609zzl
mybatis:
mapper-locations: classpath*:/mapper/*.xml
4.编写UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zzl.mapper.UserMapper">
<!--type是你的实体地址-->
<resultMap id="BaseResultMap" type="com.zzl.entity.User">
<!-- column是你数据库的book表的栏位名, property是你的实体对应的属性名-->
<result column="id" jdbcType="INTEGER" property="id"/>
<result column="username" jdbcType="VARCHAR" property="name"/>
<result column="password" jdbcType="VARCHAR" property="pad"/>
</resultMap>
<!--id记住要对应Mapper接口的方法名,不然报找不到Mapper-->
<select id="findAll" resultType="com.zzl.entity.User">
<!-- 编写sql-->
select * from user where username= #{username}
</select>
</mapper>
5.编写mapper(持久层)下的UserMapper接口
@Repository
public interface UserMapper {
User findAll(String username);
}
6.编写Service层的UserService
@Service
public class UserService {
@Resource
UserMapper userMapper;
public User find(String username){
return userMapper.findAll(username);
}
}
7.在Controller中去测试
@RestController
public class TestMybatis {
@Resource
private UserService userService;
@RequestMapping("/find/{username}")
public User find(@PathVariable String username){
return userService.find(username);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VRWZwdAj-1648108013452)(C:\Users\25412\AppData\Roaming\Typora\typora-user-images\image-20220320162329231.png)]
需要在启动类上添加@MapperScan(“com.zzl.mapper”)进行扫描
8.问题
为什么UserMapper接口可以实现自动装配
@Mapper的的功能是:在编译的时候自动生成接口实现类。但这个类是什么,在哪里
是跟UserMapper同名吗
四、sping-boot中整合mybatis-plus
1.什么是mybatis-plus(MP)
mybatis-plus是国内人员开发的mybatis增强工具,在mybatis的基础上只做增强不做改变
2.MP的功能
1.mp对基本的CURD做了封装,不用在像以前一样在mapper下写好接口后去xml中去写sql语言
只用创建接口的时候基础BaseMapper接口,service层需要继承IService,就可以使用CURD
(用service实例去调用这些方法)
insert操作:
setId、setAge等 要大写一个值
update操作:
updateById 、 updateAllColumnById(改变整行)
查询操作
selectById
删除操作
deleteById
五、spring-boot中使用事务
1.事务的概念
事务必须服从ACID原则。ACID指的是原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。 通俗理解,事务其实就是一系列指令的集合。
原子性:操作这些指令时,要么全部执行成功,要么全部不执行。只要其中一个指令执行失败,所有的指令都执行失败,数据进行回滚,回到执行指令前的数据状态。
一致性:事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定。
隔离性:在该事务执行的过程中,无论发生的任何数据的改变都应该只存在于该事务之中,对外界不存在任何影响。只有在事务确定正确提交之后,才会显示该事务对数据的改变。其他事务才能获取到这些改变后的数据。
持久性:当事务正确完成后,它对于数据的改变是永久性的。
2.依赖导入
在spring中导入了mysql和mybatis就可以使用
3.测试
在spring中使用@Transactional注解进行事务的使用
在mapper层中的UserMapper中写入一条插入方法
@Insert("insert into user (username, password) values (#{username}, #{password})")
Integer insertUser(User user);
在sevice层中使用事务(这里主动设置了一个运行时异常)
@Service
public class UserService1 {
@Resource
private UserMapper userMapper;
@Transactional
public void isertUser(User user){
userMapper.insertUser(user);
throw new RuntimeException();
}
}
在controller层中进行测试
@RestController
public class TestMybatis1 {
@Resource
private UserService1 userService1;
@PostMapping("/addUser")
public String addUser(@RequestBody User user) throws RuntimeException {
if (null!=user){
userService1.isertUser(user);
return "success";
}
else
{
return "false";
}
}
在postman进行测试
这只是事务的简单使用,就是在操作数据库时遇到异常时进行回滚操作。但是事务在遇到多线程操作时会遇到很多问题,以后实际遇到了在进行补充
六、spring-boot中使用拦截器
1.什么是拦截器
拦截器是java三大器之一(过滤器(Filter)、监听器(Listener)、拦截器(Interceptor))
简单的理解拦截器就是用来拦截你想拦截的东西:如敏感词,对控制层的请求;基于反射实现
2.定义拦截器
@Slf4j
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod handlerMethod =(HandlerMethod) handler;
Method method=handlerMethod.getMethod();
String methodName=method.getName();
log.info("====拦截到了方法;{},在该方法执行之前执行=======",method);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("执行完方法之后执行(Controller方法调用之后),但是此时还没进行视图渲染");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("整个请求都处理完了,DispatcherServlet也对应的视图,此时我们可以做一些清理工作了");
}
}
preHandle(……)
方法:该方法的执行时机是,当某个 url 已经匹配到对应的 Controller 中的某个方法,且在这个方法执行之前。 方法可以决定是否将请求放行,这是通过返回值来决定的,返回 true 则放行,返回 false 则不会向后执行。
postHandle(……)
方法:该方法的执行时机是,当某个 url 已经匹配到对应的 Controller 中的某个方法,且在执行完了该方法,但是在 DispatcherServlet 视图渲染之前。所以在这个方法中有个 ModelAndView 参数,可以在此做一些修改动作。
afterCompletion(……)
方法:顾名思义,该方法是在整个请求处理完成后(包括视图渲染)执行,这时做一些资源的清理工作,这个方法只有在 preHandle(……)
被成功执行后并且返回 true 才会被执行。
3.拦截器的配置
都是固定写法,(“/**”)表示所有路径
@Configuration
public class MyInterceptorconfig extends WebMvcConfigurationSupport {
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
super.addInterceptors(registry);
}
}
4.在Controller中测试
@Controller
@RequestMapping("/interceptor")
public class InterceptorController {
@RequestMapping("/test")
public String test() {
return "hello";
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gjCZklm1-1648108013453)(C:\Users\25412\AppData\Roaming\Typora\typora-user-images\image-20220321210942784.png)]
6.实际操作,判断user是否登录
在myInterceptor中修改preHandle()
@Slf4j
public class MyInterceptor1 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
String methodName = method.getName();
log.info("====拦截到了方法:{},在该方法执行之前执行====", methodName);
// 判断用户有没有登陆,一般登陆之后的用户都有一个对应的token
String token = request.getParameter("token");
if (null == token || "".equals(token)) {
log.info("用户未登录,没有权限执行……请登录");
return false;
}
// 返回true才会继续执行,返回false则取消当前请求
return true;
}
}
在浏览器中使用http://localhost:8080/interceptor/test
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wZqzy89H-1648108013453)(C:\Users\25412\AppData\Roaming\Typora\typora-user-images\image-20220321212039634.png)]
在浏览器中使用http://localhost:8080/interceptor/test?token=111
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b5gQ8F2x-1648108013454)(C:\Users\25412\AppData\Roaming\Typora\typora-user-images\image-20220321212146505.png)]
七、在spring-boot中整合redis
1.什么时redis
redis是一种noSql数据库(非关系型数据库)、包含多种数据结构、支持网络、基于内存、可选持久性的键值对存储数据库
- 基于内存运行,性能高效
- 支持分布式,理论上可以无限扩展
- key-value存储系统
- 开源的使用C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API
2.导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--阿里巴巴fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.35</version>
</dependency>
阿里巴巴的json可以帮助我们将对象转换成JSON格式
3.在yml中配置
redis:
host: 127.0.0.1
port: 6379
password:
即使redis的密码为空也得写上这一句。否则会报redis连接错误
4.编写service层
@Service
public class RedisService {
@Resource
private StringRedisTemplate stringRedisTemplate;
public void setString(String key,String value){
ValueOperations<String,String> valueOperations=stringRedisTemplate.opsForValue();
valueOperations.set(key, value);
}
public String getString(String key){
return stringRedisTemplate.opsForValue().get(key);
}
}
5.在Test中测试
这几个注解都必须写上
@RunWith(SpringRunner.class)
@Slf4j
@SpringBootTest
class ZzlApplicationTests {
@Resource
private RedisService redisService;
@Test
void contextLoads() {
redisService.setString("zzl","zzl");
log.info("fdfjal{}",redisService.getString("zzl"));
User user =new User(1L,"gasd","123456");
redisService.setString("userInfo", JSON.toJSONString(user));
log.info("用户信息:{}",redisService.getString("userInfo"));
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iiSMQTWo-1648108013454)(C:\Users\25412\AppData\Roaming\Typora\typora-user-images\image-20220321225214174.png)]
使用redis中的Hash、List
public void setHash(String key, String filedKey, String value){
HashOperations<String, Object, Object> hashOperations = stringRedisTemplate.opsForHash();
hashOperations.put(key,filedKey, value);
}
public String getHash(String key, String filedkey){
return (String) stringRedisTemplate.opsForHash().get(key, filedkey);
}
public Long setList(String key,String value){
ListOperations<String,String> listOperations=stringRedisTemplate.opsForList();
return listOperations.leftPush(key, value);
}
public List<String> getList(String key, long start, long end ){
return stringRedisTemplate.opsForList().range(key, start, end);
}
都是通过***Operations来实现,具体可以官方看文档
redisService.setString("zzl","zzl");
log.info("fdfjal{}",redisService.getString("zzl"));
User user =new User(1L,"gasd","123456");
redisService.setString("userInfo", JSON.toJSONString(user));
log.info("用户信息:{}",redisService.getString("userInfo"));
//相当于设置了两个条件来进行查询设置
redisService.setHash("user","name",JSON.toJSONString(user));
log.info("用户姓名:{}",redisService.getHash("user","name"));
redisService.setList("list","football");
redisService.setList("list","basketball");
List<String> valList=redisService.getList("list",0,-1);
for(String value:valList){
log.info("list中有{}",value);
}
这里的0,-1与0,1等效
6.问题
为什么各个层里的@ServIce、@Controller、@Configuration必须写上。否则会报错
fastjson怎么实现将对象转换为json格式的
这里只是最简单的redis使用。后面会继续补充
八、在spring-boot整合shrio(难点)
1.什么是shrio
Apache Shiro 是Java 的一个安全框架。Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE 环境,也可以用在JavaEE 环境。Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与Web 集成、缓存等。
2 Shiro 三大核心组件
Shiro 有三大核心的组件:Subject
、SecurityManager
和 Realm
。先来看一下它们之间的关系。
Subject:认证主体。它包含两个信息:Principals 和 Credentials。
Principals:身份。可以是用户名,邮件,手机号码等等,用来标识一个登录主体身份;
Credentials:凭证。常见有密码,数字证书等等。
SecurityManager:安全管理员。这是 Shiro 架构的核心,它就像 Shiro 内部所有原件的保护伞一样。我们在项目中一般都会配置 SecurityManager,开发人员大部分精力主要是在 Subject 认证主体上面。我们在与 Subject 进行交互的时候,实际上是 SecurityManager 在背后做一些安全操作
Realms:Realms 是一个域,它是连接 Shiro 和具体应用的桥梁,当需要与安全数据交互的时候,比如用户账户、访问控制等,Shiro 就会从一个或多个 Realms 中去查找。我们一般会自己定制 Realm,这在下文会详细说明。
3.shrio的运行流程
Step1:应用程序代码在调用 Subject.login(token)
方法后,传入代表最终用户的身份和凭证的 AuthenticationToken 实例 token。
Step2:将 Subject 实例委托给应用程序的 SecurityManager(Shiro的安全管理)来开始实际的认证工作。这里开始真正的认证工作了。
Step3,4,5:然后 SecurityManager 就会根据具体的 realm 去进行安全认证了。 从图中可以看出,realm 可以自定义(Custom Realm)。
4.依赖导入
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
5.创建数据库
这里的role_id为外键。设置外键时要注意作为外键的主键的那个表的数据要多余等于其他表否则会报错
6.自定义Realm
自定义 realm 需要继承 AuthorizingRealm 类,因为该类封装了很多方法,它也是一步步继承自 Realm 类的,继承了 AuthorizingRealm 类后,需要重写两个方法:
doGetAuthorizationInfo()
方法:用来为当前登陆成功的用户授予权限和角色
doGetAuthenticationInfo()
方法:用来验证当前登录的用户,获取认证信息
public class MyRealm extends AuthorizingRealm {
@Resource
private UserService userService;
@Override
//权限验证
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String username=(String)principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo =new SimpleAuthorizationInfo();
authorizationInfo.setRoles(userService.getRoles(username));
authorizationInfo.setStringPermissions(userService.getPermissions(username));
return authorizationInfo;
}
@Override
//身份验证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String username = (String) authenticationToken.getPrincipal();
// 根据用户名从数据库中查询该用户
User user = userService.getByUsername(username);
if(user != null) {
// 把当前用户存到session中
SecurityUtils.getSubject().getSession().setAttribute("user", user);
// 传入用户名和密码进行身份认证,并返回认证信息
AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), "myRealm");
return authcInfo;
} else {
return null;
}
}
}
7.配置shrio
自定义的 realm 写好了,接下来需要对 Shiro 进行配置了。我们主要配置三个东西:自定义 realm、安全管理器 SecurityManager 和 Shiro 过滤器。
@Configuration
@Slf4j
public class ShiroConfig {
/**
* 注入自定义的realm
* @return MyRealm
*/
@Bean
public MyRealm myRealm(){
MyRealm myRealm=new MyRealm();
log.info("==== myRealm注册完成====");
return myRealm;
}
/**
* 注入安全管理器
* @return SecurityManager
*/
@Bean
public SecurityManager securityManager() {
// 将自定义realm加进来
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(myRealm());
log.info("====securityManager注册完成====");
return securityManager;
}
/**
* 注入Shiro过滤器
* @param securityManager 安全管理器
* @return ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
// 定义shiroFactoryBean
ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
// 设置自定义的securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 设置默认登录的url,身份认证失败会访问该url
shiroFilterFactoryBean.setLoginUrl("/login");
// 设置成功之后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/success");
// 设置未授权界面,权限认证失败会访问该url
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
// LinkedHashMap是有序的,进行顺序拦截器配置
Map<String,String> filterChainMap = new LinkedHashMap<>();
// 配置可以匿名访问的地址,可以根据实际情况自己添加,放行一些静态资源等,anon表示放行
filterChainMap.put("/css/**", "anon");
filterChainMap.put("/imgs/**", "anon");
filterChainMap.put("/js/**", "anon");
filterChainMap.put("/swagger-*/**", "anon");
filterChainMap.put("/swagger-ui.html/**", "anon");
// 登录url 放行
filterChainMap.put("/login", "anon");
// “/user/admin” 开头的需要身份认证,authc表示要身份认证
filterChainMap.put("/user/admin*", "authc");
// “/user/student” 开头的需要角色认证,是“admin”才允许
filterChainMap.put("/user/student*/**", "roles[admin]");
// “/user/teacher” 开头的需要权限认证,是“user:create”才允许
filterChainMap.put("/user/teacher*/**", "perms[\"user:create\"]");
// 配置logout过滤器
filterChainMap.put("/logout", "logout");
// 设置shiroFilterFactoryBean的FilterChainDefinitionMap
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
log.info("====shiroFilterFactoryBean注册完成====");
return shiroFilterFactoryBean;
}
}
8.在control中验证
@Controller
@RequestMapping("/user")
public class UserController {
/**
* 身份认证测试接口
* @param request
* @return
*/
@RequestMapping("/admin")
public String admin(HttpServletRequest request) {
Object user = request.getSession().getAttribute("user");
return "success";
}
/**
* 角色认证测试接口
* @param request
* @return
*/
@RequestMapping("/student")
public String student(HttpServletRequest request) {
return "success";
}
/**
* 权限认证测试接口
* @param request
* @return
*/
@RequestMapping("/teacher")
public String teacher(HttpServletRequest request) {
return "success";
}
/**
* 用户登录接口
* @param user user
* @param request request
* @return string
*/
@PostMapping("/login")
public String login(User user, HttpServletRequest request) {
// 根据用户名和密码创建token
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
// 获取subject认证主体
Subject subject = SecurityUtils.getSubject();
try{
// 开始认证,这一步会跳到我们自定义的realm中
subject.login(token);
request.getSession().setAttribute("user", user);
return "success";
}catch(Exception e){
e.printStackTrace();
request.getSession().setAttribute("user", user);
request.setAttribute("error", "用户名或密码错误!");
return "login";
}
}
}
这里返回的引用了Thymeleaf模板的html页面
我们重点分析一下这个登录接口,首先会根据前端传过来的用户名和密码,创建一个 token,然后使用 SecurityUtils 来创建一个认证主体,接下来开始调用 subject.login(token)
开始进行身份认证了,注意这里传了刚刚创建的 token,就如注释中所述,这一步会跳转到我们自定义的 realm 中,进入 doGetAuthenticationInfo
方法,所以到这里,您就会明白该方法中那个参数 token 了。然后就是上文分析的那样,开始进行身份认证。
9.补充service层和mapper层
@Repository
public interface UserMapper {
User findAll(String username);
@Insert("insert into user (username, password) values (#{username}, #{password})")
Integer insertUser(User user);
@Select("select * from user where username=#{username}")
User getByUsername(String username);
@Select("select r.rolename from user u,role r " +
"where u.role_id = r.id and u.username = #{username}")
Set<String> getRoles(String username);
@Select("select p.permissionname from user u,role r,permission p " +
"where u.role_id = r.id and p.role_id = r.id and u.username = #{username}")
Set<String> getPermissions(String username);
}
@Service
public class UserService {
@Resource
UserMapper userMapper;
public User find(String username){
return userMapper.findAll(username);
}
public User getByUsername(String username) {
return userMapper.getByUsername(username);
}
public Set<String> getRoles(String username) {
return userMapper.getRoles(username);
}
public Set<String> getPermissions(String username) {
return userMapper.getPermissions(username);
}
}
rname);
@Select("select r.rolename from user u,role r " +
“where u.role_id = r.id and u.username = #{username}”)
Set getRoles(String username);
@Select("select p.permissionname from user u,role r,permission p " +
“where u.role_id = r.id and p.role_id = r.id and u.username = #{username}”)
Set getPermissions(String username);
}
```java
@Service
public class UserService {
@Resource
UserMapper userMapper;
public User find(String username){
return userMapper.findAll(username);
}
public User getByUsername(String username) {
return userMapper.getByUsername(username);
}
public Set<String> getRoles(String username) {
return userMapper.getRoles(username);
}
public Set<String> getPermissions(String username) {
return userMapper.getPermissions(username);
}
}