章节

  • Spring Boot 介绍
  • Spring Boot 开发环境搭建(Eclipse)
  • Spring Boot Hello World (restful接口)例子
  • spring boot 连接Mysql
  • spring boot配置druid连接池连接mysql
  • spring boot集成mybatis(1)
  • spring boot集成mybatis(2) – 使用pagehelper实现分页
  • spring boot集成mybatis(3) – mybatis generator 配置
  • spring boot 接口返回值封装
  • spring boot输入数据校验(validation)
  • spring boot rest 接口集成 spring security(1) – 最简配置
  • spring boot rest 接口集成 spring security(2) – JWT配置
  • spring boot 异常(exception)处理
  • spring boot 环境配置(profile)切换
  • spring boot redis 缓存(cache)集成

在教程 [spring boot rest 接口集成 spring security(1) – 最简配置] 里介绍了最简集成spring security的过程,本文将继续介绍spring boot项目中集成spring security以及配置jwt的过程。

如果不了解jwt,可以参考5分钟搞懂:JWT(Json Web Token)。

项目内容

本文将通过创建一个实际的spring boot项目来演示spring security及jwt的配置过程,项目主要内容:

  • 集成spring security;
  • 配置jwt;
  • 加载用户信息;
  • 实现几个接口,配置访问权限;
  • 最后通过Postman测试接口;

要求

  • JDK1.8或更新版本
  • Eclipse开发环境

如没有开发环境,可参考前面章节 [spring boot 开发环境搭建(Eclipse)]。

项目创建

创建spring boot项目

打开Eclipse,创建spring boot的spring starter project项目,选择菜单:File > New > Project ...,弹出对话框,选择:Spring Boot > Spring Starter Project,在配置依赖时,勾选web, security,完成项目创建。

项目依赖

要使用jwt,引入jwt jar包

        <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency>

项目配置

application.properties配置

## 服务器端口,如果不配置默认是8080端口
server.port=8096 ## jwt配置
#  签名密钥
jwt.secret=my_secret_2019
# jwt有效期(秒)
jwt.expiration=1800

代码实现

项目目录结构如下图,我们添加了几个类,下面将详细介绍。

spring security的配置:SecurityConfig.java

这是spring security的java配置类,几个主要的配置:

  • 用户信息加载配置
  • 权限不足处理配置
  • 权限配置
  • jwt过滤器配置
  • 其他如密码加密,CORS等配置
@Configuration
@EnableWebSecurity // 添加security过滤器
@EnableGlobalMethodSecurity(prePostEnabled = true) // 可以在controller方法上配置权限
public class SecurityConfig extends WebSecurityConfigurerAdapter{// 加载用户信息@Autowiredprivate UserDetailsService myUserDetailsService;// 权限不足错误信息处理,包含认证错误与鉴权错误处理@Autowiredprivate JwtAuthError myAuthErrorHandler;// 密码明文加密方式配置@Beanpublic PasswordEncoder myEncoder() {return new BCryptPasswordEncoder();}// jwt校验过滤器,从http头部Authorization字段读取token并校验@Beanpublic JwtAuthFilter myAuthFilter() throws Exception {return new JwtAuthFilter();}// 获取AuthenticationManager(认证管理器),可以在其他地方使用@Bean(name="authenticationManagerBean")@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}// 认证用户时用户信息加载配置,注入myUserDetailsService@Overridepublic void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(myUserDetailsService);}// 配置http,包含权限配置@Overrideprotected void configure(HttpSecurity http) throws Exception {http// 由于使用的是JWT,我们这里不需要csrf.csrf().disable()// 基于token,所以不需要session.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()// 设置myUnauthorizedHandler处理认证失败、鉴权失败.exceptionHandling().authenticationEntryPoint(myAuthErrorHandler).accessDeniedHandler(myAuthErrorHandler).and()// 设置权限.authorizeRequests()// 需要登录.antMatchers("/hello/hello1").authenticated()// 需要角色权限.antMatchers("/hello/hello2").hasRole("ADMIN")// 除上面外的所有请求全部放开.anyRequest().permitAll();// 添加JWT过滤器,JWT过滤器在用户名密码认证过滤器之前http.addFilterBefore(myAuthFilter(), UsernamePasswordAuthenticationFilter.class);// 禁用缓存
//      http.headers().cacheControl();  }// 配置跨源访问(CORS)@BeanCorsConfigurationSource corsConfigurationSource() {UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());return source;}
}

用户信息及用户信息服务:AuthUser.java,AuthUserService.java

加载用户信息,需要用户信息类及用户信息服务类。AuthUser继承spring的UserDetails,必须重写UserDetails的一些标准接口。注意与实体类User区别。

public class AuthUser implements UserDetails {private static final long serialVersionUID = -2336372258701871345L;//用户实体类private User user;public AuthUser(User user) {this.setUser(user);}public static Collection<? extends GrantedAuthority> getAuthoritiesByRole(String role) {Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();List<String> roles = Arrays.asList(role.split(","));if (roles.contains("user")) {authorities.add(new SimpleGrantedAuthority("ROLE_USER")); }if (roles.contains("admin")) {authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN")); } return authorities;}// 提供权限信息@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return getAuthoritiesByRole(getUser().getRole());}// 提供账号名称@Overridepublic String getUsername() {return getUser().getMobile();}// 提供密码@Overridepublic String getPassword() {return getUser().getPassword();}// 账号是否没过期,过期的用户无法认证@Overridepublic boolean isAccountNonExpired() {return true;}// 账号是否没锁住,锁住的用户无法认证@Overridepublic boolean isAccountNonLocked() {return true;}// 密码是否没过期,密码过期的用户无法认证@Overridepublic boolean isCredentialsNonExpired() {return true;}// 用户是否使能,未使能的用户无法认证@Overridepublic boolean isEnabled() {return true;}public User getUser() {return user;}public void setUser(User user) {this.user = user;}}

AuthUserService继承UserDetailsService,重写了加载用户信息接口:

@Service
public class AuthUserService implements UserDetailsService {// 加载用户信息@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 此处应从数据库加载用户信息,为简便起见,直接创建一个用户// password的值:$2a$10$EmsokMb6Vkav7m61kY0PtO.ZCLe0h.uJqVAZW7YYBpSUxd/DMkZuG,// 是明文123456使用BCryptPasswordEncoder加密的值User user = new User(1l, "abc1", username, "$2a$10$EmsokMb6Vkav7m61kY0PtO.ZCLe0h.uJqVAZW7YYBpSUxd/DMkZuG", "user");AuthUser authUser = new AuthUser(user);return (UserDetails) authUser;}
}

认证失败、鉴权失败处理:JwtAuthError.java

当认证失败,系统会抛出认证失败异常,可以配置我们自己的认证失败处理类,同样鉴权失败也可以配置我们自己的失败处理类。

JwtAuthError继承AuthenticationEntryPoint(认证失败接口)、AccessDeniedHandler(鉴权失败接口),重写了这2个接口类的失败处理方法,其实JwtAuthError可以分为2个类,我们合二为一了。

@Component
public class JwtAuthError implements AuthenticationEntryPoint, AccessDeniedHandler {@SuppressWarnings("unused")private static final org.slf4j.Logger log = LoggerFactory.getLogger(JwtAuthError.class);// 认证失败处理,返回401 json数据@Overridepublic void commence(HttpServletRequest request,HttpServletResponse response,AuthenticationException authException) throws IOException {response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);response.setContentType("application/json;charset=utf-8");response.getWriter().write("{"status":401,"message":"Unauthorized or invalid token"}");}// 鉴权失败处理,返回403 json数据@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response,AccessDeniedException accessDeniedException) throws IOException, ServletException {response.setStatus(HttpServletResponse.SC_FORBIDDEN);response.setContentType("application/json;charset=utf-8");response.getWriter().write("{"status":403,"message":"Forbidden"}");}
}

JWT过滤器

JWT过滤器每次请求应该只执行一次,所以继承OncePerRequestFilter,JWT过滤器的主要行为:

  • 对于每次请求,从http头部Authorization字段中读取jwt
  • 尝试解密jwt,如果正常解出,说明是合法用户
  • 如果是合法用户,设置认证信息,认证通过
@Component
public class JwtAuthFilter extends OncePerRequestFilter {private static final org.slf4j.Logger log = LoggerFactory.getLogger(JwtAuthFilter.class);@Autowiredprivate JwtUtil jwtUtil;private String tokenHeader="Authorization";private String tokenPrefix="Bearer";@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain chain) throws ServletException, IOException {// 从http头部读取jwtString authHeader = request.getHeader(this.tokenHeader);if (authHeader != null && authHeader.startsWith(tokenPrefix)) {final String authToken = authHeader.substring(tokenPrefix.length() + 1); // The part after "Bearer "String username = null, role = null;// 从jwt中解出账号与角色信息try {username = jwtUtil.getUsernameFromToken(authToken);role = jwtUtil.getClaimFromToken(authToken, "role", String.class);} catch (Exception e) {log.debug("异常详情", e);log.info("无效token");}// 如果jwt正确解出账号信息,说明是合法用户,设置认证信息,认证通过if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(username, null, AuthUser.getAuthoritiesByRole(role));// 把请求的信息设置到UsernamePasswordAuthenticationToken details对象里面,包括发请求的ip等auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));// 设置认证信息SecurityContextHolder.getContext().setAuthentication(auth);}}// 调用下一个过滤器chain.doFilter(request, response);}
}

User实体类(model层)

User实体类对应于数据库中的User表(我们简化了,没有连数据库)

public class User {private Long id;private String nickname;private String mobile;private String password;private String role;public User(Long id, String nickname, String mobile, String password, String role) {this.id = id;this.nickname = nickname;this.mobile = mobile;this.password = password;this.role = role;}public User() {super();}
}

LoginRequest类(model层)

登录请求类,这个类将会接受并校验用户登录时输入的账号密码,关于输入校验,可以参考 [spring boot输入数据校验(validation)]

public class LoginRequest {@SuppressWarnings("unused")private static final org.slf4j.Logger log = LoggerFactory.getLogger(LoginRequest.class);@NotNull(message="账号必须填")@Pattern(regexp = "^[1]([3][0-9]{1}|59|58|88|89)[0-9]{8}$", message="账号请输入11位手机号") // 手机号private String account;@NotNull(message="密码必须填")@Size(min=6, max=16, message="密码6~16位")private String password;private boolean rememberMe;public String getAccount() {return account;}public void setAccount(String account) {this.account = account;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public boolean isRememberMe() {return rememberMe;}public void setRememberMe(boolean rememberMe) {this.rememberMe = rememberMe;}}

AuthController类(控制层)

AuthController类实现了2个REST API:

  • login – 用户提供账号密码,如果密码正确,返回token,否则返回账号或密码错误提示;
  • refresh 输入一个合法的旧token,返回新token
@RestController
@RequestMapping("/auth")
public class AuthController {@Autowiredprivate AuthService authService;/*** login * @param authRequest* @param bindingResult* @return ResponseEntity<Result> */@RequestMapping(value = "/login", method = RequestMethod.POST, produces="application/json")public ResponseEntity<Result> login(@Valid @RequestBody LoginRequest authRequest, BindingResult bindingResult) throws AuthenticationException{if(bindingResult.hasErrors()) {         Result res = MiscUtil.getValidateError(bindingResult);return new ResponseEntity<Result>(res, HttpStatus.UNPROCESSABLE_ENTITY);}final String token = authService.login(authRequest.getAccount(), authRequest.getPassword());// Return the tokenResult res = new Result(200, "ok");res.putData("token", token);return ResponseEntity.ok(res);}/*** refresh * @param request* @return ResponseEntity<Result> */@RequestMapping(value = "/refresh", method = RequestMethod.GET, produces="application/json")public ResponseEntity<Result> refresh(HttpServletRequest request, @RequestParam String token) throws AuthenticationException{Result res = new Result(200, "ok");String refreshedToken = authService.refresh(token);if(refreshedToken == null) {res.setStatus(400);res.setMessage("无效token");return new ResponseEntity<Result>(res, HttpStatus.BAD_REQUEST);} res.putData("token", token);return ResponseEntity.ok(res);}}

HelloController类(控制层)

实现了3个REST API:

  • hello1
  • hello2
  • hello3

用于测试权限配置

@RestController
@RequestMapping("/hello")
public class HelloController {@RequestMapping(value="/hello1", method=RequestMethod.GET)public String hello1() {return "Hello1!";}@RequestMapping(value="/hello2", method=RequestMethod.GET)public String hello2() {return "Hello2!";}@RequestMapping(value="/hello3", method=RequestMethod.GET)public String hello3() {return "Hello3!";}
}

AuthService接口与AuthServiceImpl实现类(服务层)

AuthService提供对AuthController的服务

AuthService.java

public interface AuthService {User register(User userToAdd);String login(String username, String password);String refresh(String oldToken);
}

AuthServiceImpl.java

@Service
public class AuthServiceImpl implements AuthService {private static final org.slf4j.Logger log = LoggerFactory.getLogger(AuthServiceImpl.class);private AuthenticationManager authenticationManager;private UserDetailsService userDetailsService;private JwtUtil jwtUtil;@Autowiredpublic AuthServiceImpl(AuthenticationManager authenticationManager,UserDetailsService userDetailsService,JwtUtil jwtUtil) {this.authenticationManager = authenticationManager;this.userDetailsService = userDetailsService;this.jwtUtil = jwtUtil;}@Overridepublic User register(User userToAdd) {// TODO: 保存user到数据库return null;}@Overridepublic String login(String username, String password) {// 认证用户,认证失败抛出异常,由JwtAuthError的commence类返回401UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password);final Authentication authentication = authenticationManager.authenticate(upToken);SecurityContextHolder.getContext().setAuthentication(authentication);// 如果认证通过,返回jwtfinal AuthUser userDetails = (AuthUser) userDetailsService.loadUserByUsername(username);final String token = jwtUtil.generateToken(userDetails.getUser());return token;}@Overridepublic String refresh(String oldToken) {String newToken = null;try {newToken = jwtUtil.refreshToken(oldToken);} catch (Exception e) {log.debug("异常详情", e);log.info("无效token");}return newToken;}
}

其他

剩下的一些类

  • Result.java 结果封装类
  • MiscUtil.java 辅助类
  • JwtUtil.java jwt处理类,加密解密等操作

运行

Eclipse左侧,在项目根目录上点击鼠标右键弹出菜单,选择:run as -> spring boot app运行程序。 打开Postman访问接口,运行结果如下:

访问/hello/hello1接口,需要登录访问,没有带上token,返回401

登录获取token

再次访问需要登录访问的/hello/hello1接口,带上token,可以看到访问成功

访问需要admin权限的/hello/hello2接口,虽然带上token,但权限不足,可以看到返回403

总结

完整代码

springbootredis连接池配置优化_spring boot rest 接口集成 spring security(2) – JWT配置相关推荐

  1. webmvcconfigurer配置跨域_为什么加了 Spring Security 会导致 Spring Boot 跨域失效呢?...

    点击上方 IT牧场 ,选择 置顶或者星标 技术干货每日送达 作者:欧阳我去 链接:https://segmentfault.com/a/1190000019485883 作为一个后端开发,我们经常遇到 ...

  2. Spring Boot (三)集成spring security

    项目GitHub地址 : https://github.com/FrameReserve/TrainingBoot Spring Boot (三)集成spring security,标记地址: htt ...

  3. Spring Boot2 总结(二) Spring Security的基本配置

      Spring Boot对Spring Security提供了自动化配置方案,同时这也是在Spring Boot项目中使用Spring Security的优势,因此Spring Security整合 ...

  4. SpringSecurity权限管理框架系列(六)-Spring Security框架自定义配置类详解(二)之authorizeRequests配置详解

    1.预置演示环境 这个演示环境继续沿用 SpringSecurit权限管理框架系列(五)-Spring Security框架自定义配置类详解(一)之formLogin配置详解的环境. 2.自定义配置类 ...

  5. maven springboot 除去指定的jar包_Spring Boot打包瘦身 Docker 使用全过程 动态配置、日志记录配置...

    springBoot打包的时候代码和jar包打包在同一个jar包里面,会导致jar包非常庞大,在不能连接内网的时候调试代码,每次只改动了java代码就需要把所有的jar包一起上传,导致传输文件浪费了很 ...

  6. dubbo yml配置_Spring boot 的profile功能如何实现多环境配置自动切换

    通常服务端应用开发需要经过以下几个流程: 开发 -> 测试 -> RC验证 -> 上线 这就涉及到四个不同的环境,开发环境.测试环境.RC环境以及生产环境,为了避免不同环境之间相互干 ...

  7. boot spring 获取请求端口浩_Spring精华问答 | 如何集成Spring Boot?

    Spring框架是一个开源的Java平台,它提供了非常容易,非常迅速地开发健壮的Java应用程序的全面的基础设施支持.今天就让我们一起来看看关于Spring的精华问答吧. 1 Q:如何在自定义端口上运 ...

  8. spring Security 重复登录配置无效的问题

    关于spring Security重复登录的配置,百度一大堆,我这里就不啰嗦了. 今天碰到 按照网上的配置,但是 感觉配置无效,同一用户还是可以登录,不知道为什么,开始以为是自己配置的又问题.再三确认 ...

  9. Spring Boot 2.0 利用 Spring Security 实现简单的OAuth2.0认证方式1

    0. 前言 之前帐号认证用过自己写的进行匹配,现在要学会使用标准了.准备了解和使用这个OAuth2.0协议. 1. 配置 1.1 配置pom.xml 有些可能会用不到,我把我项目中用到的所有包都贴出来 ...

  10. Spring Security是什么,以及如何在Spring Boot项目中整合Spring Security并且使用它,下面我们通过一个登录案例简单介绍一下Spring Security。

    1.什么是Spring Security? 在了解Spring Security之前,我们是不是应该先思考一个问题,我们自己写的web案例一般都需要先登录,之后登录之后才能访问其他页面,或者说我们不同 ...

最新文章

  1. 八大深度学习最佳实践
  2. 利用OpenCV建立视差图像
  3. html圆圈里面问号,html,css实现问号提示信息
  4. 记录一下python绘制地图
  5. python学习 day1 (3月1日)
  6. DSP中两个延时函数的区别(转)
  7. Neo4j安装及使用
  8. 【洛谷P5019+P1969+P3078】道路铺设(暴力模拟/差分)
  9. CSS 字体加粗,导致布局宽度改变怎么处理?
  10. RAID环境中增加容量-在线扩容
  11. Zotero文献管理软件使用指南——入门篇
  12. warning: array subscript has type ‘char‘
  13. verilog实现5分频
  14. 【project】Adruino小型自平衡机器人EVA(+硬件+源代码+3D文件)
  15. 从零开始搞起 Disney BRDF源码编译(含踩坑经历)
  16. 项目管理界最经典教材——PMBOK指南,如果现在备考PMP看哪一版?
  17. SSM整合,非常详细的SSM整合
  18. 2021年安全员-A证(广西省-2021版)考试及安全员-A证(广西省-2021版)考试试卷
  19. krait和kryo_各种Java序列化性能比较
  20. 计算机科学专业和商科专业排名,2017年QS世界大学专业排名权威发布

热门文章

  1. 写给我们奔三的80后们……
  2. 图像质量评价Code和Dataset
  3. 【OpenCV学习笔记】【函数学习】五(颜色空间转换cvCvtColor()函数)
  4. 实验平均梯度与图像的模糊程度(matlab 代码)
  5. ENVI辐射定标出现Calibration requires gain and offset for each band错误提示
  6. mysql case结合group+having使用
  7. SpringAop+Mybatis 实现动态切换数据库操作
  8. Hudi on Flink 快速上手指南
  9. IT 人的国庆大阅兵,太好好好好好好看了吧 ……
  10. 再见!热血活力的深圳