96SEO 2026-02-20 05:20 11
。

小项目有Shiro的比较多#xff0c;因为相比与SpringSecurity#xff0c;Shiro的上手更加的简单。
认证#xff1a;验证当前访问系统的是不是本系统的用户#xff0c;并且要…一般来说中大型的项目都是使用SpringSecurity
来做安全框架。
小项目有Shiro的比较多因为相比与SpringSecurityShiro的上手更加的简单。
认证验证当前访问系统的是不是本系统的用户并且要确认具体是哪个用户
而认证和授权也是SpringSecurity作为安全框架的核心功能。
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-security/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency!--其他必须依赖,下面需要的--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/dependencydependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-boot-starter/artifactIdversion3.5.3.1/version/dependencydependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactId/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/dependencydependencygroupIdcom.alibaba/groupIdartifactIdfastjson/artifactIdversion1.2.58/version/dependencydependencygroupIdcom.fasterxml.jackson.datatype/groupIdartifactIdjackson-datatype-jdk8/artifactId/dependencydependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt/artifactIdversion0.9.1/version/dependencydependencygroupIdorg.apache.commons/groupIdartifactIdcommons-pool2/artifactId/dependency!--
java.lang.NoClassDefFoundError:
javax/xml/bind/DatatypeConverter--dependencygroupIdjavax.xml.bind/groupIdartifactIdjaxb-api/artifactIdversion2.3.0/version/dependencydependencygroupIdcom.sun.xml.bind/groupIdartifactIdjaxb-impl/artifactIdversion2.3.0/version/dependencydependencygroupIdcom.sun.xml.bind/groupIdartifactIdjaxb-core/artifactIdversion2.3.0/version/dependencydependencygroupIdjavax.activation/groupIdartifactIdactivation/artifactIdversion1.1.1/version/dependency
当然我们实际情况可能会加一个redis做缓存,登录之后用用户id
UsernamePasswordAuthenticationFilter:负责处理我们在登陆页面填写了用户名密码后的登陆请求。
入门案例的认证工作主要有它负责。
ExceptionTranslationFilter处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException
FilterSecurityInterceptor负责权限校验的过滤器。
AuthenticationManager接口定义了认证Authentication的方法
UserDetailsService接口加载用户特定数据的核心接口。
里面定义了一个根据用户名查询用户信息的方法。
UserDetails接口提供核心用户信息。
通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。
然后将这些信息封装到Authentication对象中。
...,在现在前后端分离项目我们肯定是要自定义登录接口不能用SpringSecurity的登录页面
是在内存中查找比较用户输入的用户名和密码那我们需要是需要查询数据库比对的。
jdbc:mysql://localhost:3306/test?characterEncodingutf-8serverTimezoneUTCusername:
authList;////JSONField(serialize
SimpleGrantedAuthority对象不支持序列化无法存入redis//////public
ArrayList();//}//第一次登录封装UserDetails对象,初始化权限列表//for
SimpleGrantedAuthority(auth);//
authorities.add(simpleGrantedAuthority);
user.getPassword();}Overridepublic
user.getUserName();}Overridepublic
下面bool值全部响应为true,UserDetail对象返回校验过程中会因为没权限报错return
redisTemplate(RedisConnectionFactory
connectionFactory){RedisTemplateObject,
RedisTemplate();template.setConnectionFactory(connectionFactory);FastJsonRedisSerializer
FastJsonRedisSerializer(Object.class);//
使用StringRedisSerializer来序列化和反序列化redis的key值template.setKeySerializer(new
StringRedisSerializer());template.setValueSerializer(serializer);//
Hash的key也采用StringRedisSerializer的序列化方式template.setHashKeySerializer(new
StringRedisSerializer());template.setHashValueSerializer(serializer);template.afterPropertiesSet();return
UUID.randomUUID().toString().replaceAll(-,
SignatureAlgorithm.HS256;SecretKey
System.currentTimeMillis();Date
Date(nowMillis);if(ttlMillisnull){ttlMillisJwtUtil.JWT_TTL;}long
签发时间.signWith(signatureAlgorithm,
第二个参数为秘钥.setExpiration(expDate);}/***
parseJWT(eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIyOTY2ZGE3NGYyZGM0ZDAxOGU1OWYwNjBkYmZkMjZhMSIsInN1YiI6IjIiLCJpc3MiOiJzZyIsImlhdCI6MTYzOTk2MjU1MCwiZXhwIjoxNjM5OTY2MTUwfQ.NluqZnyJ0gHz-2wBIari2r3XpPp06UMn4JS2sWHILs0);String
claims.getSubject();System.out.println(subject);
System.out.println(claims);}/***
Base64.getDecoder().decode(JwtUtil.JWT_KEY);SecretKey
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();}}
clazz;static{ParserConfig.getGlobalInstance().setAutoTypeSupport(true);}public
SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);}Overridepublic
TypeFactory.defaultInstance().constructType(clazz);}
实现UserDatailService接口注入到spring容器中这样就会在SpringSecurity的认证流程中调用我们自定义的实现类。
用用户名查询对应UserLambdaQueryWrapperUser
LambdaQueryWrapper();queryWrapper.eq(User::getUserName,username);//
this.getOne(queryWrapper);if(user
将查询到user封装到自定义UserDeatail中return
}我们看下图我们输入的用户名和密码最终会传入UserDetailService
方法返回UserDtail对象。
返回过程中会与UserDetail中的password和username进行比对,不同则报错。
注意如果要测试需要往用户表中写入用户数据并且如果你想让用户的密码是明文存储需要在密码前加{noop}。
例如
这样登陆的时候就可以用sg作为用户名1234作为密码来登陆了。
在实际中项目中我一般不会明文存储而是采用PasswordEncoder加密的方式;
我们一般使用SpringSecurity为我们提供的BCryptPasswordEncoder。
我们只需要使用把BCryptPasswordEncoder对象注入Spring容器中SpringSecurity就会使用该PasswordEncoder来进行密码校验
接下我们需要自定义登陆接口然后让SpringSecurity对这个接口放行,让用户访问这个接口的时候不用登录也能访问。
在接口中我们通过AuthenticationManager的authenticate方法来进行用户认证,所以需要在SecurityConfig中配置把AuthenticationManager注入容器。
认证成功的话要生成一个jwt放入响应中返回。
并且为了让用户下回请求时能通过jwt识别出具体的是哪个用户我们需要把用户信息存入redis可以把用户id作为key。
userService;PostMapping(/login)Result
userService.login(user);}}public
authenticationManager;Autowiredprivate
进行认证UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken(user.getUserName(),
user.getPassword());Authentication
authenticationManager.authenticate(authenticationToken);//
如果认证通过、给出对应提示if(Objects.isNull(authenticate)){throw
认证通过使用userid生成jwtUserDetailsImpl
authenticate.getPrincipal();String
udi.getUser().getId().toString();String
JwtUtil.createJWT(userId);HashMapString,
HashMap();map.put(token,token);//
把完整用户信息存入redisredisTemplate.opsForValue().set(login:
BCryptPasswordEncoder();}Overrideprotected
{http//关闭csrf.csrf().disable()//不通过Session获取SecurityContext.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()//
允许匿名访问.antMatchers(/user/login).anonymous()//
除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated();}BeanOverridepublic
super.authenticationManagerBean();}
jdbc:mysql://localhost:3306/test?characterEncodingutf-8serverTimezoneUTCusername:
以下跨域设置,postman测试是完全没有问题的。
但是我们是为了前后端分离会有问题。
当登录之后携带token的请求头被jwt过滤器捕获解析之后获得userId,用userId将从redis拿出UserDetailImpl封装到
SecurityContextHolder.getContext()中,被SpringSecurity过滤链捕获到证明没有问题因此放行访问资源。
redisTemplate;Overrideprotected
doFilterInternal(HttpServletRequest
设置跨域response.setHeader(Access-Control-Allow-Origin,
修改携带cookie,PSresponse.setHeader(Access-Control-Allow-Methods,
GET,POST,DELETE,PUT);response.setHeader(Access-Control-Allow-Headers,
预检请求缓存时间秒即在这个时间内相同的预检请求不再发送直接使用缓存结果。
response.setHeader(Access-Control-Max-Age,
request.getHeader(Authorization);if(StringUtils.isEmpty(authorization)){filterChain.doFilter(request,response);return;}String
authorization.substring(6);//解析tokenString
RuntimeException(token非法);}//从redis中获取用户信息String
redisTemplate.opsForValue().get(redisKey);if(Objects.isNull(udi)){throw
RuntimeException(用户未登录);}//存入SecurityContextHolder//获取权限信息封装到Authentication中UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken(udi,null,udi.getAuthorities());SecurityContextHolder.getContext().setAuthentication(authenticationToken);//放行filterChain.doFilter(request,
在SpringSecurity中会使用默认FiterSecuritInterceptor来进行权限校验而它又是从SpringSecurityContex中Authentication来获取其中的权限信息。
判断当前用户是否拥有访问当前资源权限。
因此我们只需要将权限信息存储到Authentication中即可设置资源的访问权限。
EnableGlobalMethodSecurity(prePostEnabled
RequestMapping(/hello)PreAuthorize(hasAnyAuthority(test))
我们之前UserDetailServiceImpl中loadUserByUsername()中返回的UserDetailImpl对象我们之前创建时并没传入权限字段我们先自定义权限字段模拟用户权限列表。
用用户名查询对应UserLambdaQueryWrapperUser
LambdaQueryWrapper();queryWrapper.eq(User::getUserName,username);//
this.getOne(queryWrapper);if(user
RuntimeException(用户名或密码错误);}ListString
暂且封装这样权限字段authlist.add(test);authlist.add(test0);return
UserDetailsImpl(user,authlist);}
相应的UserDetailImpl中也需要修改seucrity过滤器链是从getAuthorities()方法中来获取用户权限字段的。
SimpleGrantedAuthority对象不支持序列化无法存入redispublic
第一次登录封装UserDetails对象,初始化权限列表for
SimpleGrantedAuthority(auth);authorities.add(simpleGrantedAuthority);
user.getPassword();}Overridepublic
user.getUserName();}Overridepublic
JsonInclude(JsonInclude.Include.NON_NULL)
-54979041104113736L;TableIdprivate
http://mybatis.org/dtd/mybatis-3-mapper.dtd
namespacecom.qhx.springsecuritydemo.mapper.MenuMapperselect
resultTypejava.lang.StringSELECTm.permsFROMuser_role
用用户名查询对应UserLambdaQueryWrapperUser
LambdaQueryWrapper();queryWrapper.eq(User::getUserName,username);//
this.getOne(queryWrapper);if(user
RuntimeException(用户名或密码错误);}ListString
menuMapper.selectPermsByUserId(user.getId());return
UserDetailsImpl(user,authlist);}
JSON.toJSONString(result);response.setStatus(200);response.setContentType(application/json);response.getWriter().write(jsonResult);}
Result.error(您认证错误/请检查你的用户名或密码是否正确,
JSON.toJSONString(result);response.setStatus(200);response.setContentType(application/json);response.getWriter().write(jsonResult);}
EnableGlobalMethodSecurity(prePostEnabled
BCryptPasswordEncoder();}Autowiredprivate
accessDeniedHandler;Autowiredprivate
authenticationEntryPoint;Overrideprotected
{http//关闭csrf.csrf().disable()//不通过Session获取SecurityContext.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()//
允许匿名访问.antMatchers(/user/login).anonymous()//
除上面外的所有请求全部需要鉴权认证.anyRequest().authenticated();//
添加自定义token过滤器到链中http.addFilterBefore(jwtFilter,
UsernamePasswordAuthenticationFilter.class);//
添加自定义异常处理器http.exceptionHandling().accessDeniedHandler(accessDeniedHandler).authenticationEntryPoint(authenticationEntryPoint);}BeanOverridepublic
super.authenticationManagerBean();}
上面除了我们SpringSecurity异常那么还有一些其他异常我们不希望每次捕获并手动抛出因此可以转化为自定义异常用SpringBoot的全局异常处理器捕获并且抛出
我们现在是用postman测试但是未来项目是设计到前后端分离项目交互的都伴随着跨域的问题。
后端能接受到前端原生request对象,response对象,都涉及到跨域的问题。
设置允许跨域的路径registry.addMapping(/**)//
设置允许跨域请求的域名.allowedOriginPatterns(*)//
是否允许cookie.allowCredentials(true)//
设置允许的header属性.allowedHeaders(*)//
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback