我在使用spring进行开发时,通常是使用 aop+jwt 模式来对调用者身份进行确认。前几天接触到一个开源商城源码(github地址)里面使用spring security +jwt 来进行权限的验证。但是源码中只实现了简单的用户名密码验证,关于权限的略过了。虽然以前了解过spring security但是没有实际使用过,借着这个机会整合了一下spring security jwt。(可以拿起就用:smirk:) github源码地址

spring security 是基于spring的一个web安全框架。一般来说,web应用的安全性包括用户认证和用户授权两个部分。用户认证常见的就是用户名密码验证。用户授权则指的是查看用户是否有权限调用资源。

用户认证

对于用户认证,我们自定义的话通常需要自己实现 PasswordEncoder UserDetail两个类。PasswordEncoder主要实现了密码的加密,以及密码的比较(登陆时用户密码与数据库存储的密码)。我实现的PasswordEncoder代码如下

package com.lichaobao.springsecurityjwt.component;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.password.PasswordEncoder;/*** @author lichaobao* @date 2018/12/22* @QQ 1527563274*/
public class MyPasswordEncoder implements PasswordEncoder {private static final Logger LOGGER = LoggerFactory.getLogger(MyPasswordEncoder.class);/*** 自定义密码加密(出于示例,本代码没有对密码进行加密,直接返回原密码)* @param charSequence 需要加密的密码* @return 加密后的密码*/@Overridepublic String encode(CharSequence charSequence) {LOGGER.info("now encode password :{}",charSequence.toString());return charSequence.toString();}/*** 比较加密后的密码与数据库中的密码是否匹配* @param charSequence 用户登陆传来的密码* @param s 数据库中存储的密码* @return true 匹配 false 不匹配*/@Overridepublic boolean matches(CharSequence charSequence, String s) {LOGGER.info("matchs charSequence :{} and password :{}",charSequence,s);return encode(charSequence).equals(s);}
}复制代码

对于UserDetails类来说主要起到了封装用户信息的作用,包括用户的基本信息以及拥有的权限信息的封装。默认UserDetails的生成类是 UserDetailsService。 这个接口中提供了loadUserByUsername(String username)方法UserDetailService源码如下

package org.springframework.security.core.userdetails;public interface UserDetailsService {UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
复制代码

在本例中 我们通过修改WebSecurityConfigAdapter中的UserdetailsServices实现代码如下

    /*** 在次代码中完成用户基本信息的查询比如用户名 密码 权限等封装后 返回* 此方法的入口 为 userDetailsService.loadUserByUsername(String username)* @return UserDetail*/@Bean@Overrideprotected UserDetailsService userDetailsService() {return username ->{if(users.containsKey(username)){return new MyUserDetails(username,users.get(username),permissions.get(username));}throw new UsernameNotFoundException("用户名错误");};}
复制代码

权限验证

自定义权限验证我们通过自定义AccesDecisionVoter类来实现。关键代码如下

/*** @author lichaobao* @date 2018/12/22* @QQ 1527563274*/
public class RoleBasedVotor implements AccessDecisionVoter {private static final Logger LOGGER = LoggerFactory.getLogger(RoleBasedVotor.class);@Overridepublic boolean supports(ConfigAttribute configAttribute) {return true;//根据自己的逻辑修改  不能直接return false否则验证不通过}/*** 主要验证逻辑* ROLE_ANONYMOUS 代表所有人可以访问 这是 spring security 自动生成的 可以自定义* @param authentication 用户信息* @param o  可以从这里拿到url* @param collection 访问资源需要的权限在本例中由于我们将url作为验证依据所以为用到collection* @return ACCESS_DENIED(-1)无权限 ACCESS_GRANTED(1)有权限*/@Overridepublic int vote(Authentication authentication, Object o, Collection collection) {FilterInvocation fi = (FilterInvocation) o;String url = fi.getRequestUrl();LOGGER.info("url :{}",url);if(authentication == null){return ACCESS_DENIED;}Collection<? extends GrantedAuthority> authorities = extractAuthorities(authentication);Iterator iterator = authorities.iterator();while (iterator.hasNext()){GrantedAuthority ga = (GrantedAuthority) iterator.next();LOGGER.info(ga.getAuthority());if(equalsurl(url,ga.getAuthority())||"ROLE_ANONYMOUS".equals(ga.getAuthority())){return ACCESS_GRANTED;}}return ACCESS_DENIED;}@Overridepublic boolean supports(Class aClass) {return true;//根据自己的逻辑修改  不能直接return false否则验证不通过}/*** 获得用户权限信息* @param authentication* @return*/Collection<? extends GrantedAuthority> extractAuthorities(Authentication authentication) {LOGGER.info("extractAuthorites:{}",authentication.getAuthorities());return authentication.getAuthorities();}/*** 比较权限 权限 /** 代表 以下所有能访问 /* 代表以下一级能访问 如 用户权限为 /test/** 则能访问 /test/a /test/b/c* 如用户权限为 /test/* 则能访问 /test/a 而 /test/b/c则不能访问* @param url 访问的url* @param urlpermission 拥有的权限* @return boolean*/static boolean equalsurl(String url,String urlpermission) {url = url.startsWith("/") ? url.substring(1):url;urlpermission = urlpermission.startsWith("/")?urlpermission.substring(1):urlpermission;if("**".equals(urlpermission)){return true;}else if("*".equals(urlpermission)){return url.split("/").length == 1;}else if(urlpermission.endsWith("/**")){String afterUrl =  urlpermission.substring(0,urlpermission.length()-3);return url.startsWith(afterUrl);}else if(urlpermission.endsWith("/*")){String afterUrl = urlpermission.substring(0,urlpermission.length()-2);String[] urlPiece = url.split("/");return url.startsWith(afterUrl)&&urlPiece.length == 2;}return url.equals(urlpermission);}
}复制代码

验证登陆

在登陆逻辑的代码中,我们需要通过登陆接口接收到的用户名、密码生成UsernamePasswordAuthenticationToken 为接下来的验证提供一个桥梁。注意密码要根据自定义实现的PasswordEncoder中的加密方法或着其他自己实现的加密方法进行加密要保证加密后和数据库中的密码相对应。然后 通过调用authenticationManager中的authenticate方法进行验证。具体代码如下

@Service
public class SignServiceImpl implements SignService {private static final Logger LOGGER = LoggerFactory.getLogger(SignService.class);@AutowiredAuthenticationManager authenticationManager;@AutowiredUserDetailsService userDetailsService;@AutowiredJwtUtils jwtUtils;@AutowiredPasswordEncoder passwordEncoder;/*** 登陆 登陆出现错误抛出错误 用catch接受即可* @param username 用户名* @param password 密码* @return String token*/@Overridepublic String login(String username, String password) {String token = null;/*** 封装 注意密码加密*/UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(username,passwordEncoder.encode(password));try{Authentication authentication = authenticationManager.authenticate(authenticationToken);SecurityContextHolder.getContext().setAuthentication(authentication);/*** 加载数据库中的用户名密码 主要逻辑为UserdetailsServices中的代码*/userDetailsService.loadUserByUsername(username);token = jwtUtils.generateToken(username);}catch (Exception e){e.printStackTrace();LOGGER.info("认证失败 :{}",e.getMessage());}return token;}
}
复制代码

验证登陆凭证

具体实现为继承OncePerRequestFilter方法实现自己的Filter ,通过解析token获得用户信息,然后比对用户权限。代码如下:

/*** @author lichaobao* @date 2018/12/22* @QQ 1527563274*/
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);@AutowiredJwtUtils jwtUtils;@AutowiredUserDetailsService userDetailsService;@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain) throws ServletException, IOException {String token = request.getHeader("token");if(token != null && SecurityContextHolder.getContext().getAuthentication() == null){String username = jwtUtils.getUserNameFromToken(token);UserDetails userDetails = userDetailsService.loadUserByUsername(username);UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());LOGGER.info("UserDetails :{},permissions:{}",userDetails.getUsername(),userDetails.getAuthorities());authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));LOGGER.info("authenticated user :{}",username);LOGGER.info("already filter name:{}",super.getAlreadyFilteredAttributeName());SecurityContextHolder.getContext().setAuthentication(authenticationToken);}filterChain.doFilter(request,response);}
}复制代码

配置security

具体实现为继承WebSecurityConfigurerAdapter方法 根据自己的需要重写逻辑

/*** @author lichaobao* @date 2018/12/22* @QQ 1527563274*/
@Configuration
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {@AutowiredMyAccessDeineHandler myAccessDeineHandler;@AutowiredMyAuthenticationEntryPoint myAuthenticationEntryPoint;/*** 模拟数据库用户*/private static Map<String,String> users;/*** 模拟权限*/private static Map<String,List<String>> permissions;static {users = new HashMap<>();permissions = new HashMap<>();users.put("a","a");String[] aper = new String[]{"/a/**","/test/all"};permissions.put("a",Arrays.asList(aper));users.put("b","b");String[] bper = new String[]{"/b/**","test/all"};permissions.put("b",Arrays.asList(bper));users.put("admin","password");String[] adminPer = new String[]{"/**"};permissions.put("admin",Arrays.asList(adminPer));}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable()//禁用csrf 因为使用jwt不需要.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)//禁用session.and().authorizeRequests().accessDecisionManager(accessDecisionManager())//加载自己的accessDecisionManager 用到了RoleBasedVotor.antMatchers(HttpMethod.GET,"/","/*.html","/favicon.ico","/**/*.html","/**/*.css","/**/*.js","/swagger-resources/**","/v2/api-docs/**").permitAll()//允许访问所有界面资源.antMatchers("/login","/register").permitAll()//允许访问登陆注册接口  然后  "/login"与"/register"的权限为"ROLE_ANONYMOUS".antMatchers(HttpMethod.OPTIONS).permitAll()//跨域请求 会有一个OPTIONS 请求  全部允许.anyRequest()//其他任何都需要验证.authenticated();/*** 禁用缓存*/http.headers().cacheControl();/*** 配置自定义的Filter*/http.addFilterBefore(jwtAuthenticationTokenFilter(),UsernamePasswordAuthenticationFilter.class);/*** 配置自定义的无权限以及用户名密码错误返回结果*/http.exceptionHandling().accessDeniedHandler(myAccessDeineHandler).authenticationEntryPoint(myAuthenticationEntryPoint);}/*** 配置 userDetailsService 以及passwordEncoder;* @param auth* @throws Exception*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());}/*** 在次代码中完成用户基本信息的查询比如用户名 密码 权限等封装后 返回* 此方法的入口 为 userDetailsService.loadUserByUsername(String username)* @return UserDetail*/@Bean@Overrideprotected UserDetailsService userDetailsService() {return username ->{if(users.containsKey(username)){return new MyUserDetails(username,users.get(username),permissions.get(username));}throw new UsernameNotFoundException("用户名错误");};}@Beanpublic PasswordEncoder passwordEncoder(){return new MyPasswordEncoder();}@Beanpublic JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){return new JwtAuthenticationTokenFilter();}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}/*** 具体使用RoleBasedVotor方法* @return*/@Beanpublic AccessDecisionManager accessDecisionManager(){List<AccessDecisionVoter<? extends Object>> decisionVoters= Arrays.asList(new WebExpressionVoter(),new RoleBasedVotor(),new AuthenticatedVoter());return new UnanimousBased(decisionVoters);}
}
复制代码

转载于:https://juejin.im/post/5cf47e9ae51d45590a445aef

spring boot +spring security + jwt 实现认证模块相关推荐

  1. springboot jwt token前后端分离_基于Spring Boot+Spring Security+JWT+Vue前后端分离的开源项目...

    一.前言 最近整合Spring Boot+Spring Security+JWT+Vue 完成了一套前后端分离的基础项目,这里把它开源出来分享给有需要的小伙伴们 功能很简单,单点登录,前后端动态权限配 ...

  2. Spring Boot + Spring Security + JWT + 微信小程序登录

    Spring Boot + Spring Security + JWT + 微信小程序登录整合教程 参考文章 文章目录 整合思想 整合步骤 1. AuthenticationToken 2. Auth ...

  3. spring security+jwt 登录认证

    spring security+jwt 登录认证 1.综述 2.版本与环境 3.架构 4.数据库认证逻辑图 5.案例 security+jwt 5.1引入依赖 5.2新建工具类 5.2新建组件类 5. ...

  4. Spring Boot+Spring Security+JWT 实现token验证

    Spring Boot+Spring Security+JWT 实现token验证 什么是JWT? JWT的工作流程 JWT的主要应用场景 JWT的结构 SpringBoot+Spring Secur ...

  5. Spring Boot + WebSocketClient + wss协议证书认证 + 客户端心跳重连机制

    近期公司项目中要对接第三方的WebSocket服务获取数据,本来以为是很简单的工作,但问题是服务方提供的是"wss"协议,需要证书认证,为此查阅了很多博客,都没有解决, 最后还是自 ...

  6. Spring Boot从零入门2_核心模块详述和开发环境搭建

    本文属于原创,转载注明出处,欢迎关注微信小程序小白AI博客 微信公众号小白AI或者网站 https://xiaobaiai.net 文章目录 1 前言 2 名词术语 3 Spring Boot核心模块 ...

  7. Spring Boot+Spring Cloud实现itoken项目

    itoken项目简介 开发环境 操作系统: Windows 10 Enterprise 开发工具: Intellij IDEA 数据库: MySql 5.7.22 Java SDK: Oracle J ...

  8. Spring Boot+Spring Cloud基础入门(一)简单介绍

    转自:https://blog.csdn.net/mingwei_cheng/article/details/80939833 在经历了毕业的摧残后,终于又有时间来更新博客了,毕业设计项目是写了一个基 ...

  9. Spring Boot Spring MVC 异常处理的N种方法

    默认行为 根据Spring Boot官方文档的说法: For machine clients it will produce a JSON response with details of the e ...

最新文章

  1. 飞凌OK6410开发板移植u-boot官方最新版u-boot-2012.10.tar.bz2
  2. pandas 中 的drop函数
  3. 【每日随笔】使用 you-get 获取网页中的视频资源 ( Python 环境安装 | you-get 工具使用 )
  4. .net中excel遇到的一些问题
  5. WEB前端:浏览器(IE+Chrome+Firefox)常见兼容问题处理【01】
  6. 【Python】推荐10个好用到爆的Jupyter Notebook插件,让你效率飞起
  7. 腾讯游戏数据应用微服务实战
  8. 工作406- Error:Node Sass version 5.0.0 is incompatible with ^4.0.0 问题解决
  9. 【环境搭建000】详细图解ubuntu 上安装配置eclips
  10. 前端工程师的CI进阶之路
  11. c fun函数求n个整数的平均值_c语言题目(求阶乘)
  12. 系统学习机器学习之线性判别式(一)
  13. ROC曲线及AUC值
  14. 一个很难的sql面试题
  15. 如何调整Exadata DB节点文件系统大小
  16. iOS修改手游服务器数据,iOS 教你修改运动步数(基于Healthkit)
  17. 电镀面积计算机公式,教你正确的计算电镀中施镀面积方法。
  18. python竖线_python 读取竖线分隔符的文本方法
  19. 利用输入法输入汉字,如何统计字数
  20. Servlet execution threw an exception

热门文章

  1. qt在GUI显示时,将调试信息输出到控制台的设置
  2. UVA 558 SPFA 判断负环
  3. 管理者每天要做的十件事
  4. 从Postman到ApiPost——码农闰土
  5. 03-19 分布式测试-Selenium Grid
  6. python组合和继承_Python基础系列讲解——继承派生和组合的概念剖析
  7. python小例子-Python 常用小例子
  8. linux系统创建lvm卷,Linux逻辑卷LVM实现
  9. mysql自定义函数重载_python pyMysql 自定义异常 函数重载
  10. html5和html的区别是什么?学HTML5要不要学html?