springboot-安全认证security+jwt总结

article/2025/10/29 5:27:01

目录

一、背景

二、基本jar依赖引入

三、security模块

1、编写配置类

2、UnauthorizedHandler代码

3、security验证用户名和密码的部分

四、jwt模块

1、jwt原理部分

2、jwt一共需要四个类

五、总结


一、背景

要做一个后台管理系统,会引入多个系统,这就需要做用户认证和权限管理。用户认证通过token来实现,市面上的技术有很多,我这里仅仅来说明一下security+jwt的一种实现过程,没有做页面,需要做页面的同学自行实现。

有些容易入坑的点,我看别的资料没有说太清楚,这里记录下,希望能帮助到跳坑的同学。

二、基本jar依赖引入

<!-- security -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency><!-- jwt依赖 -->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>

总结:

我看有的帖子也引入了jjwt的API、impl包,我这里没有用到,实现权限控制和token校验两个完全够用

三、security模块

1、编写配置类

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {//操作用户@AutowiredFfUserService ffUserService;//token校验@AutowiredJwtAuthenPreFilter jwtAuthenPreFilter;/***token异常*/@AutowiredUnauthorizedHandler unauthorizedHandler;//配置放行策略@Value("${jwt.security.antMatchers}")private String antMatchers;/*** 密码加密算法** @return*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder(10);}/*** 验证用户来源,主要是验证账号和密码** @param auth* @throws Exception*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(ffUserService).passwordEncoder(passwordEncoder());}/*** 忽略策略* @param web* @throws Exception*/@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers(antMatchers.split(","));}/*** 用户授权* @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated()       // 所有的验证都需要验证.and().csrf().disable()                      // 禁用 Spring Security 自带的跨域处理              
// 定制我们自己的 session 策略:调整为让 Spring Security 不创建和使用 session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().exceptionHandling().authenticationEntryPoint(unauthorizedHandler);// 将自定义的过滤器添加在指定过滤器之前http.addFilterBefore(jwtAuthenPreFilter, FilterSecurityInterceptor.class);// 禁用缓存http.headers().cacheControl();}}

总结:

  • 需要放行的可以在两个地方配置,第一种如上图;第二种可以在第二个configure中配置。比如:.antMatchers(antMatchers.split(",")).permitAll()
  • 在第二个configure中这里特别注意一下配置的顺序,exceptionHandling().authenticationEntryPoint(myAuthenticationEntryPoint())如果放在前面会导致放行策略不生效。
  • security是通过用户名和密码来实现认证的,不一定能满足实际业务需要,所以要扩展,前后端分离目前常用的做法就是基于usertoken的,即上面的自定义的jwtAuthenPreFilter过滤器
  • addFilterAfter: 将自定义的过滤器添加在指定过滤器之后
  • addFilterBefore:将自定义的过滤器添加在指定过滤器之前
  • addFilter:添加一个过滤器,但必须是Spring Security自身提供的过滤器实例或其子过滤器
  • addFilterAt: 添加一个过滤器在指定过滤器位置

2、UnauthorizedHandler代码

@Component
@Slf4j
public class UnauthorizedHandler implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {//用户登录时身份认证未通过if(e instanceof BadCredentialsException){//用户登录时身份认证失败ResultUtil.writeJavaScript(httpServletResponse, ErrorCodeEnum.TOKEN_INVALID.getCode(), e.getMessage());}else if (e instanceof InsufficientAuthenticationException){//缺少请求头参数,Authorization传递是token值,所以是参数是必须的ResultUtil.writeJavaScript(httpServletResponse, ErrorCodeEnum.NO_TOKEN.getCode(), ErrorCodeEnum.NO_TOKEN.getMessage());}else{//用户token无效ResultUtil.writeJavaScript(httpServletResponse, ErrorCodeEnum.TOKEN_INVALID.getCode(), ErrorCodeEnum.TOKEN_INVALID.getMessage());}}
}

注意:

  • 接口中的逻辑异常要捕获,不然会被拦截报token异常就不美观了,也可以完善这个类。
  • 也可以扩展单独的异常处理模块做统一处理,但是业务异常我还是推荐根据业务场景来单独处理,一味的追求统一处理不见得都是好事。

3、security验证用户名和密码的部分

  • 网上资料很多,大家自己补充

四、jwt模块

1、jwt原理部分

  • 网上资料很多,大家自己补充

2、jwt一共需要四个类

  • JwtAuthenPreFilter:token校验和有关业务,这部分可以根据自己项目来实现
@Component
@Slf4j
public class JwtAuthenPreFilter extends OncePerRequestFilter {@Autowiredprivate JwtTokenUtil jwtTokenUtil;//@Autowired//private RedisUtil redisUtil;/*** 防止filter被执行两次*/private static final String FILTER_APPLIED = "__spring_security_JwtAuthenPreFilter_filterApplied";@Value("${jwt.header:Authorization}")private String tokenHeader;@Value("${jwt.tokenHead:Bearer}")private String tokenHead;/*** 距离快过期多久刷新令牌*/@Value("${jwt.token.subRefresh:#{10*60}}")private Long subRefresh;// 不需要认证的接口@Value("${jwt.security.antMatchers}")private String antMatchers;@Autowiredprivate FfUserService ffUserService ;public JwtAuthenPreFilter() {}@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {if (httpServletRequest.getAttribute(FILTER_APPLIED) != null) {filterChain.doFilter(httpServletRequest, httpServletResponse);return;}httpServletRequest.setAttribute(FILTER_APPLIED, true);//过滤掉不需要token验证的urlSkipPathAntMatcher skipPathRequestMatcher = new SkipPathAntMatcher(Arrays.asList(antMatchers.split(",")));if (skipPathRequestMatcher.matches(httpServletRequest)) {filterChain.doFilter(httpServletRequest, httpServletResponse);} else {try {//1.判断是否有效 2.判断是否过期 3.如果未过期的,且过期时间小于10分钟的延长过期时间,并在当前response返回新的header,客户端需替换此令牌String authHeader = httpServletRequest.getHeader(this.tokenHeader);if (authHeader != null && authHeader.startsWith(tokenHead)) {final String authToken = authHeader.substring(tokenHead.length());JWTUserDetail userDetail = jwtTokenUtil.getUserFromToken(authToken);if (ObjectUtils.isEmpty(userDetail)) {log.info("令牌非法,解析失败{}!", authToken);throw new BadCredentialsException(ErrorCodeEnum.TOKEN_INVALID.getMessage());}if (jwtTokenUtil.isTokenExpired(authToken)) {log.info("令牌已失效!{}", authToken);throw new BadCredentialsException(ErrorCodeEnum.TOKEN_INVALID.getMessage());}//令牌快过期生成新的令牌并设置到返回头中,客户端在每次的restful请求如果发现有就替换原值if (new Date(System.currentTimeMillis() - subRefresh).after(jwtTokenUtil.getExpirationDateFromToken(authToken))) {String resAuthToken = jwtTokenUtil.generateToken(userDetail);httpServletResponse.setHeader(tokenHeader, tokenHead + resAuthToken);}JwtTokenUtil.LOCAL_USER.set(userDetail);UserDetails userDetails = ffUserService.loadUserByUsername(userDetail.getLoginName());UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));SecurityContextHolder.getContext().setAuthentication(authentication);} else {//需要校验却无用户tokenlog.info("无header请求-->" + httpServletRequest.getRequestURI());throw new InsufficientAuthenticationException(ErrorCodeEnum.NO_TOKEN.getMessage());}} catch (Exception e) {//log.info("令牌解析失败!", e);throw new BadCredentialsException(ErrorCodeEnum.TOKEN_INVALID.getMessage());}filterChain.doFilter(httpServletRequest, httpServletResponse);//调用完成后清除JwtTokenUtil.LOCAL_USER.remove();}}
}
  • JWTUserDetail:token转换成user类
@Data
public class JWTUserDetail implements Serializable {/*** 登陆用户编号*/private long userId;/*** 登陆用户账户名称(可能为手机号邮箱或者名称用户维度唯一)*/private String loginName;/*** 登陆用户类型*/private UserType userType;/*** 登陆用户凭证*/private String jwtToken;/*** 登陆时间*/private Date loginTime;private static ObjectMapper mapper = new ObjectMapper();public enum UserType {User("USER", 1),Operator("OPT", 2),Erp("ERP", 3);private String name;private int index;private UserType(String name, int index) {this.name = name;this.index = index;}@Overridepublic String toString() {return this.name;}public static String getName(int index) {for (UserType c : UserType.values()) {if (c.getIndex() == index) {return c.getName();}}return null;}public String getName() {return name;}public int getIndex() {return index;}}public static JWTUserDetail fromJson(String json) throws JsonProcessingException {return mapper.readValue(json,JWTUserDetail.class);//JSONObject.parseObject(json, JWTUserDetail.class);}public String toJson() throws JsonProcessingException {return mapper.writeValueAsString(this);//.toJSONString(this);}
}
  • JwtTokenUtil:token工具
@Component
public class JwtTokenUtil implements Serializable {private static final long serialVersionUID = -5883980282405596071L;public static final ThreadLocal<JWTUserDetail> LOCAL_USER = new ThreadLocal<>();public final static String JWT_TOKEN_PREFIX = "jwt:%s:%d";private final String JWT_LOGIN_NAME = "JWT_LOGIN_NAME";private final String JWT_LOGIN_TIME = "JWT_LOGIN_TIME";private final String JWT_LOGIN_USERID = "JWT_LOGIN_USERID";private final String JWT_LOGIN_USERTYPE = "JWT_LOGIN_USERTYPE";//签名方式private final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;//密匙@Value("${jwt.security.secret}")private String secret;@Value("${jwt.access_token:#{30*24*60*60}}")private Long access_token_expiration;public String getLoginNameFromToken(String token) {return getClaimsFromToken(token).getSubject();}/*** 根据token 获取用户信息*/public JWTUserDetail getUserFromToken(String token) {JWTUserDetail jwtUserDetails = new JWTUserDetail();Claims claims = getClaimsFromToken(token);jwtUserDetails.setUserId(claims.get(JWT_LOGIN_USERID, Long.class));jwtUserDetails.setLoginName(claims.get(JWT_LOGIN_NAME, String.class));jwtUserDetails.setUserType(Enum.valueOf(JWTUserDetail.UserType.class, (String) claims.get(JWT_LOGIN_USERTYPE)));jwtUserDetails.setLoginTime(new Date(claims.get(JWT_LOGIN_TIME, Long.class)));jwtUserDetails.setJwtToken(token);return jwtUserDetails;}/*** 根据用户信息生成token** @param user* @return*/public String generateToken(JWTUserDetail user) {Map<String, Object> claims = new HashMap<>();claims.put(JWT_LOGIN_NAME, user.getLoginName());claims.put(JWT_LOGIN_TIME, user.getLoginTime());claims.put(JWT_LOGIN_USERID, user.getUserId());claims.put(JWT_LOGIN_USERTYPE, user.getUserType());return Jwts.builder()//一个map 可以资源存放东西进去.setClaims(claims)//  用户名写入标题.setSubject(user.getLoginName()).setId(UUID.randomUUID().toString()).setIssuedAt(new Date())//过期时间.setExpiration(new Date(System.currentTimeMillis() + access_token_expiration * 1000))//数字签名.signWith(SIGNATURE_ALGORITHM, secret).compact();}/*** 根据token 获取生成时间*/public Date getCreatedDateFromToken(String token) {return getClaimsFromToken(token).getIssuedAt();}/*** 根据token 获取过期时间*/public Date getExpirationDateFromToken(String token) {return getClaimsFromToken(token).getExpiration();}/*** token 是否过期*/public Boolean isTokenExpired(String token) {return getExpirationDateFromToken(token).before(new Date());}/**** 解析token 信息* @param token* @return*/private Claims getClaimsFromToken(String token) {return Jwts.parser()//签名的key.setSigningKey(secret)// 签名token.parseClaimsJws(token).getBody();}}
  • SkipPathAntMatcher:token校验的放行策略
@Slf4j
public class SkipPathAntMatcher implements RequestMatcher {private List<String> pathsToSkip;public SkipPathAntMatcher(List<String> pathsToSkip) {this.pathsToSkip = pathsToSkip;}@Overridepublic boolean matches(HttpServletRequest request) {if (!ObjectUtils.isEmpty(pathsToSkip)) {for (String s : pathsToSkip) {AntPathRequestMatcher antPathRequestMatcher = new AntPathRequestMatcher(s);if (antPathRequestMatcher.matches(request)) {return true;}}}return false;}
}

五、总结

  • 放行策略有两个地方一定要都配置,1.security要配置,2.token的过滤器也要配置。
  • security是校验URL是否有权限访问或者直接放行,我这里没有写用户角色和权限,是因为后面我计划给不同的角色返回不同的菜单,通过菜单来区分。如果你的业务需要可以单独设置
  • token过滤器是配置这次请求是否token有效。security放行的,token也要放行。比如登录和注册页面,这时用户没有任何权限的,所以都放行。

最后,欢迎大家关注我的个人公众号,我会把经历分享出来,助你了解圈内圈外事。

同时也欢迎大家添加个人微信【shishuai860505】,我拉大家进我的读者交流群。


http://chatgpt.dhexx.cn/article/F7uhut6Z.shtml

相关文章

璀璨盛启·焕美升级 上颜集团杭州医学旗舰中心盛大启幕

2023年4月26日&#xff0c;「璀璨盛启焕美升级」上颜杭州医学旗舰中心启幕盛典在杭州滨江钱龙大厦耀新启幕。上颜用审美、匠心构建城市的活力与色彩&#xff0c;致力于为客户带来全新的美丽方式和一流的品质服务。 &#xff08;上台剪彩嘉宾从左至右依次为&#xff09;上颜集团…

尚品汇项目难点及解决

v-router重写push、replace方法 问题描述&#xff1a;当页面再次进入同一个路由地址时&#xff0c;控制台报错 解决方法&#xff1a;修改vue-router的配置文件&#xff0c;也就是router/index.js文件 //重写VueRouter.prototype身上的push方法了 VueRouter.prototype.push …

技术·融合·治理|众享链网2021发布会暨试运行启动仪式成功举办

2021年6月&#xff0c;工业和信息化部、中央网信办共同发布《关于加快推动区块链技术应用和产业发展的指导意见》&#xff0c;建设多方协同共赢的先进产业链&#xff0c;打造多技术融合的区块链生态体系已成为新的发展方向。如何能让所有人都有机会参与这场区块链的产业变革&am…

尚品汇前端项目难点

尚品汇前端项目 1.如何运行后浏览器自动打开 ​ 解决方法&#xff1a;找到package.json 找到 script serve 后面加 –open 2.打开后发现第一次打开网页显示http://0.0.0.0:8080/ 的网页可能暂时无法连接&#xff0c;或者它已永久性地移动到了新网址。 解决方法&#xff1a;…

九龙证券|券商春季策略扎堆来袭 风格切换成焦点

2月以来&#xff0c;国泰君安、中信建投、国金证券等10余家券商组织相继发布2023年春季战略。综合来看&#xff0c;组织对A股持达观预期&#xff0c;未来两三个月A股商场或迎来重要切换。风格上&#xff0c;“中心财物&#xff0c;生长接力”或许成为上半年装备主线&#xff0c…

分击合进,锦江之星酒店与白玉兰酒店再领投资热潮

2022年11月11日&#xff0c;「山水画中游&#xff0c;暇享好时光」品牌品鉴会在广西桂林隆重召开。锦江酒店&#xff08;中国区&#xff09;旗下两大酒店品牌锦江之星酒店和白玉兰酒店携手亮相本次活动。 &#xff08;品牌矩阵品鉴会活动现场&#xff09; 后疫情时代&#xff…

盛世昊通打造好品艺拍线上交易平台,助力推广中国艺术文化

艺术品交易市场这两年的发展可谓是空前热烈&#xff0c;诸多艺术品皆迎来变现需求。新时代背景下&#xff0c;有数字技术的加持&#xff0c;数字文化资产作为数字经济发展的重要组成部分&#xff0c;成就了文化交易市场蓝海。 为响应国务院“全面复兴传统文化”&#xff0c;“提…

上海亚商投顾:沪指高开低走涨0.45% 大消费王者归来

上海亚商投顾前言&#xff1a;无惧大盘大跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 市场情绪 沪指今日高开低走&#xff0c;深成指、创业板指较为强势&#xff0c;盘中均涨超2%&#xff0c;午后涨幅有所收窄。…

《扬帆优配》互联互通扩容提升港股市场活力

作为国际最敞开的自由港和重要的国际金融中心之一&#xff0c;香港衔接内地与国际的优势不断稳固。自2022年以来&#xff0c;互联互通机制屡获打破性进展&#xff0c;港交所发挥“超级联系人”的效果不断提高。 今年3月&#xff0c;跟着港股通进一步扩容&#xff0c;一批成长型…

活动回顾丨研发效能度量线下沙龙圆满举办

2月18日&#xff0c;由跬智信息&#xff08;Kyligence&#xff09;联合甄知科技主办的研发效能度量线下沙龙圆满举办。本次沙龙在 Kyligence 上海总部举办&#xff0c;Kyligence 联合创始人兼 CTO 李扬、腾讯 Tech Lead 茹炳晟&#xff0c;以及甄知科技创始人兼 CTO 张礼军在现…

2023COSP深圳户外展行业高峰论坛即将召开!博洋优选也将参加

2023COSP深圳户外展将在3月17日—19日&#xff0c;于深圳福田会展中心举办&#xff0c;此次展会邀请到知名品牌企业领头人、商协会负责人、圈内大咖&#xff1b;由市场趋势的洞察入手&#xff0c;多维度、多角度进行深度分享&#xff0c;引导行业从业者&#xff0c;以更宏观、更…

精彩回顾 | 客户体验官俱乐部(CEOC)首届主题论坛在深圳圆满落幕

2021年10月29日&#xff0c;客户体验官俱乐部&#xff08;Customer Experience Officer Club&#xff0c;CEOC&#xff09;首届主题论坛在深圳益田威斯汀酒店圆满落幕。本次论坛由客户体验官俱乐部主办&#xff0c;瀚一数据科技&#xff08;深圳&#xff09;有限公司和才博&…

尚好房 04_服务拆分

尚好房&#xff1a;服务拆分 一、业务介绍 1、项目模块划分 根据前面的介绍&#xff0c;目前我们的系统规划了3个dubbo服务提供者模块&#xff1a;权限服务、房源服务与会员服务&#xff0c;及2个服务消费者模块&#xff1a;尚好房管理平台&#xff08;web-admin&#xff09…

纯正体验,极致商务 | 丽亭酒店聚焦未来赛道,实现共赢发展

10月28日&#xff0c;锦江酒店(中国区)“齐鲁集锦 共话未来”投资人交流会在济南盛大召开&#xff0c;面向华东地区投资人&#xff0c;行业专家、商旅客、品牌代表齐聚一堂&#xff0c;共同聚焦酒店市场投资新价值&#xff0c;商讨新时代酒店行业新机遇&#xff0c;多维探索酒店…

2023尚上优选-社区团购 优选电商Spring Cloud Alibaba

尚上优选2023最新企业级微服务架构项目 分布式微服务后端VUE、小程序 尚上优选是真实居住社区内居民团体的一种互联网线上线下购物消费行为&#xff0c;是依托真实社区的一种区域化、小众化、本地化、网络化的团购形式。简而言之&#xff0c;它是依托社区和团长社交关系实现生…

尚品汇项目搭建

文章目录 一些问题大体流程一、基础配置路由7&#xff09;路由的跳转8&#xff09;路由传参9)重写replace|和push方法10&#xff09;router和route axios引入进度条vuex三级联动防抖节流 Home组件轮播图mock共用组件Carousel 二.search页面内查看接口格式发送请求获取数据面包屑…

BBS论坛

BBS论坛的搭建 第一步:下载 Discuz_X3.2_SC_UTF8.zip 压缩包到/var/www/westos.com/bbs/html下。 mkdir /var/www/westos.com/bbs/html -p 第二步&#xff1a;解压的到 readme upload utility 三个文件夹&#xff0c;删除压缩包 第三步&#xff1a;编辑/etc/httpd/conf.d中的…

尚好房 07_前端房源展示

尚好房&#xff1a;前端房源展示 一、分页显示房源列表 1、效果 2、项目搭建 2.1 创建项目 在web项目中创建子工程web-front 2.2 pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0&…

高地创新“至臻和鸣”体系甄选客户答谢会暨公益论坛隆重举行

2022年1月5日,高地创新服务产业集团“至臻和鸣”体系甄选客户答谢会暨物业服务智造幸福生活公益论坛于上海豫园“海上梨园”召开。复星蜂巢合伙人、高地城市服务产业集团董事长冉飞,复星蜂巢副总裁、高地城市服务产业集团执行总裁陈煜宇,复星副首席赋能官、公共事务与企业传…

关于万物悦享推广案例

关于万物悦享推广案例 项目介绍 万物悦享是一家改变传统消费模式的公司&#xff0c;致力于让消费者在衣食住行都能把消费变成开心享受的事情。该公司通过消费增值、绿色积分、12倍通证强制出局、卷轴和撸实现这一目标。在通证经济下&#xff0c;消费者可以通过获得通证再赚回…