Spring Boot
分布式开发:
创建空的Spring Boot
项目命名ChongStudy-serve
,删除``src文件夹仅保留
pom.xml`文件,此为父类项目,用于聚合子类项目。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.9</version>
<relativePath/>
</parent>
<groupId>com.chong</groupId>
<artifactId>ChongStudy-serve</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ChongStudy-serve</name>
<description>聚合服务</description>
<packaging>pom</packaging>
<!-- modules用于绑定子类项目-->
<modules>
<module>chongStudy-article</module>
<module>chongStudy-member</module>
<module>chongStudy-common</module>
</modules>
分别创建Spring Boot
项目
-
chongStudy-common
:为公共依赖模块,该模块一般将其他子类项目中公共引用的工具类、依赖、bean放入该模块。 -
chongStudy-member
:为子类项目,一般为开发中的一个功能模块。
<!-- 通过在dependencies中加入依赖来引入公共模块-->
<dependency>
<groupId>com.chong</groupId>
<artifactId>chongStudy-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
chongStudy-article
:为子类项目,一般为开发中的一个功能模块,与chongStudy-member
操作相同。
AOP
:
非注解类型:
- 添加
AOP
依赖。
<!--aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 创建
MyAop.java
/**
* @author wangchong
* @create 2023-04-21-20:16
*/
@Aspect
@Component
public class MyAop {
// 定义切入点,这里是任意项目总的com.chong中的任意controller实现aop
@Pointcut("execution(* com.chong.*.controller.*.*(..))")
public void log(){
System.out.println("切入点签名");
}
// 前置通知:在被切入方法执行之前执行
@Before("log()")
public void doBefore(JoinPoint jp) throws Throwable{
//接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//记录请求内容
System.out.println("URL:"+request.getRequestURI().toString());
System.out.println("HTTP_METHOD:"+request.getMethod());
System.out.println("CLASS_METHOD"+jp);
}
// 后置通知:在被切入方法执行之后执行
@After("log()")
public void doAfter(JoinPoint jp) throws Throwable{
System.out.println("后置通知:返回joinPoint内容"+jp.toString());
}
// 返回通知:在被切入方法返回结果之后执行
@AfterReturning(returning="ret",pointcut="log()")
public void doAfterReturning(Object ret) throws Throwable{
System.out.println("返回通知:返回值为"+ret);
}
// 异常通知:在被切入方法抛出异常之后执行
@AfterThrowing(throwing="ex",pointcut="log()")
public void doAfterThrowing(JoinPoint jp, Exception ex) throws Throwable{
System.out.println("异常通知:方法异常执行");
System.out.println("产生异常的方法"+jp);
System.out.println("异常种类"+ex);
}
// 环绕通知:围绕着被切入方法执行,参数必须为ProceedingJoinPoint
@Around("log()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable{
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = attributes.getRequest();
System.out.println(request.getRequestURI());
System.out.println(request.getRemoteAddr());
String classMethod = joinPoint.getSignature().getDeclaringTypeName()+"."+joinPoint.getSignature().getName();
//得到方法执行所需的参数
Object[] args = joinPoint.getArgs();
System.out.println("传入方法的参数"+args);
//调用方法:proceed()
System.out.println("执行方法前");
Object ret = joinPoint.proceed(args);
System.out.println("执行方法后");
System.out.println("执行完方法的返回值"+ret);
return ret;
}
}
- 注意以上
ServletRequestAttributes
作用:用来读取调用接口用户的信息,需要用到以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
注解类型:
- 创建
annotation
包并在包内创建MyLogAnnotation.java
接口。
/**
* @author wangchong
* @create 2023-04-21-21:33
*/
//表示此注解可以标注在类和方法上
@Target({ElementType.METHOD,ElementType.TYPE})
//运行时生效
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLogAnnotation {
//定义一个变量,可以接收参数
String desc() default " ";
}
- 在
HelloController.java
类中添加方法
@RequestMapping("helloAnnotation")
// 表有这个注解分方法会被增强
@MyLogAnnotation(desc = "测试注解类型:helloAnnotation")
public Object helloAnnotation(){
return "hello annotation";
}
- 在
aop
包下创建MyAopAnnotation.java
/**
* @author wangchong
* @create 2023-04-21-21:32
*/
@Aspect
@Component
public class MyAopAnnotation {
// 定义切入点,这里绑定自定义的注解
@Pointcut(value = "@annotation(com.chong.common.annotation.MyLogAnnotation)")
public void logAnnotation(){
System.out.println("切入点签名");
}
// 前置通知:在被切入方法执行之前执行,注解中加入@annotation(myLogAnnotation),且参数中加入MyLogAnnotation myLogAnnotation
@Before("logAnnotation() && @annotation(myLogAnnotation)")
public void doBefore(JoinPoint jp,MyLogAnnotation myLogAnnotation) throws Throwable{
System.out.println("doBefore=======>"+myLogAnnotation.desc());
}
// 后置通知:在被切入方法执行之后执行
@After("logAnnotation() && @annotation(myLogAnnotation)")
public void doAfter(JoinPoint jp,MyLogAnnotation myLogAnnotation) throws Throwable{
System.out.println("doAfter=======>"+myLogAnnotation.desc());
}
// 返回通知:在被切入方法返回结果之后执行
@AfterReturning(returning="ret",pointcut="logAnnotation() && @annotation(myLogAnnotation)")
public void doAfterReturning(Object ret,MyLogAnnotation myLogAnnotation) throws Throwable{
System.out.println("doAfterReturning=======>"+myLogAnnotation.desc());
}
// 异常通知:在被切入方法抛出异常之后执行
@AfterThrowing(throwing="ex",pointcut="logAnnotation() && @annotation(myLogAnnotation)")
public void doAfterThrowing(JoinPoint jp, Exception ex,MyLogAnnotation myLogAnnotation) throws Throwable{
System.out.println("doAfterThrowing=======>"+myLogAnnotation.desc());
}
// 环绕通知:围绕着被切入方法执行,参数必须为ProceedingJoinPoint
@Around("logAnnotation() && @annotation(myLogAnnotation)")
public Object doAround(ProceedingJoinPoint joinPoint,MyLogAnnotation myLogAnnotation) throws Throwable{
//得到方法执行所需的参数
Object[] args = joinPoint.getArgs();
System.out.println("传入方法的参数"+args);
//调用方法:proceed()
System.out.println("执行方法前");
Object ret = joinPoint.proceed(args);
System.out.println("执行方法后");
System.out.println("执行完方法的返回值"+ret);
System.out.println("doAround=======>"+myLogAnnotation.desc());
return ret;
}
}
Handler拦截器:
- 创建类并实现
HandlerInterceptor
接口,其中有三个方法需要重现分别为preHandle()
、afterCompletion()
以及postHandle()
,分别对应前拦截、后拦截以及进入ModelAndView
后和返回前拦截。
public class MyHandlerInterceptor implements HandlerInterceptor {
public static Logger logger = (Logger) LoggerFactory.getLogger(MyHandlerInterceptor.class);
/**
* 前拦截,在进入ModelAndView之前拦截
* 使用场景:身份校验,身份授权
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
logger.info("preHandle:前拦截");
return true;
}
/**
* 后拦截,该方法在handler方法执行完之后执行
* 使用场景有:日志记录,管理,异常统一处理等等
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
logger.info("afterCompletion:后拦截");
}
/**
* 在进入ModelView后返回之前
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
logger.info("postHandle:post拦截");
}
}
- 创建配置类并实现
WebMvcConfigurer
接口,重现addInterceptors()
方法,需加上配置类注解
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
/**
* 添加拦截器以及添加拦截路径
*/
registry.addInterceptor(new MyHandlerInterceptor()).addPathPatterns("/**");
}
}
Filter过滤器:
- 过滤器实现方案有两种,一种是基于
serlvet
的注解@WebFilter
进行配置,另一种是使用Spring Boot
提供的FilterRegistrationBean
注册自定义过滤器,由于该项目是使用Spring Boot
开发所以使用后者。 - 创建
filter
包,并在包中创建MyFilter.java
实现Filter
接口,代码如下:
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("-----您已进入过滤器-----");
// 传给下一个过滤器
filterChain.doFilter(servletRequest,servletResponse);
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
- 在
config
包下添加FilterConfig.java
文件,代码如下:
@Configuration
public class FilterConfig {
@Bean
// 过滤器1
public FilterRegistrationBean myFilter1(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new MyFilter());
//设置过滤器执行次序
filterRegistrationBean.setOrder(1);
//设置过滤器名称
filterRegistrationBean.setName("myFilter");
//配置过滤器规则,请求"/helloAop"时进行过滤
filterRegistrationBean.addUrlPatterns("/helloAop");
//配置多个过滤规则
// Collection<String> urlPatterns = new ArrayList();
// urlPatterns.add("/helloError");
// urlPatterns.add("/helloAnnotation");
// filterRegistrationBean.setUrlPatterns(urlPatterns);
return filterRegistrationBean;
}
// @Bean
// 过滤器2
// public FilterRegistrationBean myFilter2(){
// FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new MyFilter2());
//
// //添加过滤器
// filterRegistrationBean.addUrlPatterns("/helloAop");
// //......
// return filterRegistrationBean;
// }
}
拦截器和过滤器的区别:
Filter
是基于函数回调doFilter()
,而拦截器是基于AOP
思想的。Filter
只在Servlet
前后起作用,而拦截器能够深入到方法前后、异常抛出前后等。- 依赖于
Servlet
容器即Web
应用中,而拦截器不依赖与Servlet
容器所以可以运行多个环境。 - 在接口调用的生命周期里,拦截器可以多次调用,而Filter只能在容器初始化时初始化一次,每次请求时创建新的
action
,一次请求只能调用一次Filter
。 Filter
和拦截器的执行顺序:过滤前->拦截前->action执行->拦截后->过滤后。
Listener监听器
监听器:当某个事件触发,就会执行方法块。
使用场景:
- 监听
Servlet
上下文用来初始化一些数据。 - 监听
HTTP Session
用来获取当前在线人数。 - 监听客户端请求的
ServletRequest
对象来获取用户访问信息等等。
监听器接口:
ApplicationListener
,监听全局。SmartApplicationListener
,是ApplicationListener
的子类,功能强于ApplicationListener
ApplicationListener
需求:在Spring
容器初始化完成之后开始监听,并打印日志。
实现:
@Component
public class MyListener implements ApplicationListener<ContextRefreshedEvent> {
public static Logger logger = LoggerFactory.getLogger(MyFilter.class);
private static boolean isFlag = false;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (!isFlag){
isFlag = true;
logger.info("我已监听到了");
}
}
}
自定义监听器需要实现
ApplicationListener<ContextRefreshedEvent>
接口。
ContextRefreshedEvent
是一个事件,会在Spring
容器初始化完成后触发,所以监听器会在Spring
容器初始化完成之后开始监听,所以这就是所谓的全局监听。
isFlag
是一个启动标志,因为web
应用会出现父子容器,所以会触发两次监听器,需要一个标志,保证任务执行一次。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1glXYnMG-1683168740355)(C:\Users\wangchong\AppData\Roaming\Typora\typora-user-images\1682126409451.png)]
监听Servlet
上下文对象
- 监听
servlet
上下文对象可以用来初始化数据,用于缓存。 - 创建实体类
User.java
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
private Integer uid;
private String username;
private String pwd;
}
- 创建
service
层userService.java
,模拟数据库查询数据。
@Service
public class UserService {
/**
* 获取用户信息
* @return
*/
public User getUser(){
//实际业务场景下,会从数据库中获取数据
return new User(10001,"张三","123456");
}
}
- 然后写一个监听器,实现
ApplicationListener<ContextRefreshedEvent>
接口,重写onApplicationEvent
方法,将ContextRefreshedEvent
对象传进去。如果我们想在加载或刷新应用上下文时,也重新刷新下我们预加载的资源,就可以通过监听ContextRefreshedEvent
来做这样的事情。
@Component
public class MyListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
//首先获去application上下文
ApplicationContext applicationContext = contextRefreshedEvent.getApplicationContext();
//获取对应的service
UserService userService = applicationContext.getBean(UserService.class);
User user = userService.getUser();
//获取application的域对象,将查到的信息放到application域中
ServletContext application = applicationContext.getBean(ServletContext.class);
application.setAttribute("user",user);
}
}
- 首先通过
contextRefreshedEvent
来获取application
上下文,再通过application
上下文来获取UserService
这个bean
,项目中可以根据实际业务场景,也可以获取其他的bean
,然后再调用自己的业务代码获取相应的数据,最后存储到application
域中,这样前端在请求相应数据的时候,我们就可以直接从application
域中获取信息,减少数据库的压力。下面写一个Controller
直接从application
域中获取user
信息来测试一下。
@RestController
@RequestMapping("/listener")
public class UserController {
@GetMapping("/user")
public User getUser(HttpServletRequest request){
ServletContext application = request.getServletContext();
User user = (User) application.getAttribute("user");
return user;
}
}
监听Http
会话Session
对象
- 监听器还有一个比较常用的地方就是用来监听
session
对象,来获取在线用户数量,现在有很多开发者都有自己的网站,监听session
来获取当前在下用户数量是个很常见的使用场景。
@Component
public class MyHttpSessionListener implements HttpSessionListener {
//日志记录
private static final Logger logger = LoggerFactory.getLogger(MyHttpSessionListener.class);
//记录用户在线的数量
public Integer count = 0;
@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
logger.info("=========新用户上线了========");
count++;
httpSessionEvent.getSession().getServletContext().setAttribute("count",count);
}
@Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
logger.info("==========用户下线了=========");
count--;
httpSessionEvent.getSession().getServletContext().setAttribute("count",count);
}
- 该
Controller
中是直接获取当前session
中的用户数量,启动服务器,在浏览器中输入localhost:8080/listener/total
可以看到返回的结果是1,再打开一个浏览器,请求相同的地址可以看到 count 是 2 ,这没有问题。
@RestController
@RequestMapping("/listener")
public class HttpSessionListenerController {
@GetMapping("/total")
public String getTotalUser(HttpServletRequest request){
Integer count = (Integer) request.getSession().getServletContext().getAttribute("count");
return "当前在线人数:"+count;
}
}
- 如果关闭一个浏览器再打开,理论上应该还是2,但是实际测试却是 3。原因是
session
销毁的方法没有执行(可以在后台控制台观察日志打印情况),当重新打开时,服务器找不到用户原来的session
,于是又重新创建了一个session
,解决方法:处理逻辑是让服务器记得原来那个session
,即把原来的sessionId
记录在浏览器中,下次再打开时,把这个sessionId
传过去,这样服务器就不会重新再创建了。重启一下服务器,在浏览器中再次测试一下,即可避免上面的问题。我们可以将上面的Controller
方法改造一下:
@RestController
@RequestMapping("/listener")
public class HttpSessionListenerController {
@GetMapping("/total")
public String getTotalUser(HttpServletRequest request, HttpServletResponse response){
Cookie cookie;
try {
cookie = new Cookie("JSESSIONID", URLEncoder.encode(request.getSession().getId(),"utf-8"));
//设置Cookie有效期
cookie.setMaxAge(2*24*60*60);
response.addCookie(cookie);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
Integer count = (Integer) request.getSession().getServletContext().getAttribute("count");
return "当前在线人数:"+count;
}
}
监听客户端请求Servlet Request
对象
@Component
public class MyServletRequestListener implements ServletRequestListener {
private static final Logger logger = LoggerFactory.getLogger(MyServletRequestListener.class);
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequestEvent.getServletContext();
logger.info("sessionId 为:",httpServletRequest.getRequestedSessionId());
logger.info("request url为",httpServletRequest.getRequestURL());
httpServletRequest.setAttribute("name","张三");
}
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
logger.info("request end");
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequestEvent.getServletContext();
logger.info("request域中保存的name值为:",httpServletRequest.getAttribute("name"));
}
}
- Controller层
@RestController
@RequestMapping("/listener")
public class ServletRequestListenerController {
@GetMapping("/request")
public String getRequestInfo(HttpServletRequest httpServletRequest){
System.out.println("requestListener中的初始化数据:"+httpServletRequest.getAttribute("name"));
return "成功";
}
}
Springboot
中自定义监听事件
自定义监听器的实现步骤:
第一步:自定义事件。创建自定义事件类,继承
ApplicationEvent
对象第二步:自定义监听器。创建自定义监听器类监听自定义事件,需要实现
ApplicationListener
接口,重写onApplicationEvent
方法第三步:设定事件触发类,使用
applicationContext.publishEvent(event)
手动触发监听事件
- 自定义事件,需要继承
ApplicationEvent
对象,在事件中定义一个User
对象来模拟数据,构造方法中将User
对象传进来初始化。如下:
//自定义事件
public class MyEvent extends ApplicationEvent {
//模拟数据
private User user;
public MyEvent(Object source,User user) {
super(source);
this.user = user;
}
/**
* @description 获取事件中用户信息
* @return User
*/
public User getUser(){
return user;
}
}
- 自定义监听器,来监听上面定义的
MyEvent
事件,自定义监听器需要实现ApplicationListener
接口即可。如下:
//自定义监听器
@Component
public class MyDiyListener implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent myEvent) {
//获取事件的信息
User user = myEvent.getUser();
System.out.println("用户名:"+user.getUsername());
System.out.println("密码:"+user.getPwd());
}
}
- 需要手动发布事件,这样监听器才能监听到,这需要根据实际业务场景来触发,针对本文的例子,我写个触发逻辑,如下:
@Service
public class UserService {
@Resource
private ApplicationContext applicationContext;
/**
* 获取用户信息
* @return
*/
public User getUser(){
//实际业务场景下,会从数据库中获取数据
return new User(10001,"张三","123456");
}
/**
* 发布事件
* @return
*/
public User getUser2(){
User user = new User(10002, "李四", "147258");
//发布事件
MyEvent myEvent = new MyEvent(this,user);
applicationContext.publishEvent(myEvent);
return user;
}
}
- 在 service 中注入
ApplicationContext
,在业务代码处理完之后,通过ApplicationContext
对象手动发布MyEvent
事件,这样我们自定义的监听器就能监听到,然后处理监听器中写好的业务逻辑。 最后,在Controller
中写一个接口来测试一下:
@RestController
@RequestMapping("/listener")
public class GetRequestInfo {
@Autowired
private UserService userService;
@GetMapping("/diy")
public String getInfo(HttpServletRequest httpServletRequest){
User user2 = userService.getUser2();
System.out.println("信息为:"+user2.getUsername());
return "success";
}
}
文件的上传与下载:
文件上传
- 创建Controller层代码,代码如下:
@PostMapping("/upload")
public Map<String,Object> upload(MultipartFile file, HttpServletRequest req) {
Map<String, Object> result = new HashMap<>();
String originName = file.getOriginalFilename();
// 如果名字不是以md结尾,返回报错
if(!originName.endsWith(".md")){
result.put("status","error");
result.put("msg","文件类型不对");
return result;
}
// 保存文件路径,临时路径
String format = sdf.format(new Date());
String realPath = req.getServletContext().getRealPath("/")+ format;
File folder = new File(realPath);
if (!folder.exists()){
folder.mkdirs();
}
// 文件名可能重名,搞一个新的名字
String newName = UUID.randomUUID().toString()+".md";
try {
file.transferTo(new File(folder,newName));
String url = req.getScheme()+"://"+req.getServerName()+":"+req.getServerPort()+format+newName;
result.put("status","success");
result.put("url",url);
} catch (IOException e) {
result.put("status","error");
result.put("msg",e.getMessage());
}
return result;
}
PostMan
测试:首先编写好post
请求的端口,然后使用Body
中的form-data
,注意KEY
后选File
如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aaNLxBf3-1683168740356)(C:\Users\wangchong\AppData\Roaming\Typora\typora-user-images\1682173585859.png)]
- 前端
Vue
,代码如下:
template>
<div class="home">
<el-upload
action="http://localhost:8888/fileOperationController/upload"
:on-preview="handlePreview"
:limit="10"
accept=".md"
>
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">
只能上传md文件,且不超过500kb
</div>
</el-upload>
</div>
</template>
<script>
export default {
data() {
return {
fileList: [],
};
},
methods: {
handlePreview(file) {
console.log(file);
window.open(file.response.url)
},
},
};
</script>
Spring Sevurity
WebSecurityConfigurerAdapter
:自定义Security
策略AuthenticationManagerBuilder
:自定义认证策略@EnableWebSecurity
:开启WebSecurity
模式UsernamePasswordAuthenticationFilter
:负责处理我们在登陆页面填写了用户名密码后的登陆请求。入门案例的认证工作主要有它负责。ExceptionTranslationFilter
: 处理过滤器链中抛出的任何AccessDeniedException
和AuthenticationException
。FilterSecurityInterceptor
: 负责权限校验的过滤器。- Spring Security完整流程如下图:
Spring Security
过滤器链中有哪些过滤器及它们的顺序,如下图:
添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-startersecurity</artifactId>
</dependency>
Spring Security的两个主要目标是“认证”和“授权(访问控制)”
-
“认证”(
Authentication
):验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户 -
“授权”(
Authorization
):经过认证后判断当前用户是否有权限进行某个操作
认证:
Authentication
接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息。AuthenticationManager
接口:定义了认证Authentication
的方法UserDetailsService
接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法。UserDetails
接口:提供核心用户信息。通过UserDetailsService
根据用户名获取处理的用户信息要封装成UserDetails
对象返回。然后将这些信息封装到Authentication
对象中。
登录
- 自定义登录接口,调用
ProviderManager
的方法进行认证 如果认证通过生成jwt
,把用户信息存入redis
中。 - 自定义
UserDetailsService
,在这个实现类中去查询数据库。
校验:
- 定义
jwt
认证 - 过滤器获取
token
解析token
获取其中的userid
- 从
redis
中获取用户信息,存入SecurityContextHolder
实现:
①添加依赖
<!--redis依赖-->
<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.33</version>
</dependency>
<!--jwt依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
②添加Redis
配置
FastJsonRedisSerializer<T>
:Redis
使用FastJson
序列化,将对象转换为可用于存储的数据流。RedisConfig
:配置Redis
③响应类
ResponseResult<T>
:将数据进行包装。
④ 工具类
JWT
工具类:生成token
等等。RedisCache
:Redis
缓存类WebUtil
:将字符串渲染到客户端
⑤ 创建实例类,Dao
层,进行数据库查询等操作。
⑥ 创建一个类实现UserDetailsService
接口,重写其中的方法。
⑦ 因为UserDetailsService
方法的返回值是UserDetails
类型,所以需要定义一个类,实现该接口,把用户信息封装在其中。
⑧ 添加Security
配置信息
SecurityConfig
②添加Redis
配置
FastJsonRedisSerializer<T>
:Redis
使用FastJson
序列化,将对象转换为可用于存储的数据流。RedisConfig
:配置Redis
③响应类
ResponseResult<T>
:将数据进行包装。
④ 工具类
JWT
工具类:生成token
等等。RedisCache
:Redis
缓存类WebUtil
:将字符串渲染到客户端
⑤ 创建实例类,Dao
层,进行数据库查询等操作。
⑥ 创建一个类实现UserDetailsService
接口,重写其中的方法。
⑦ 因为UserDetailsService
方法的返回值是UserDetails
类型,所以需要定义一个类,实现该接口,把用户信息封装在其中。
⑧ 添加Security
配置信息
SecurityConfig