springbootredis连接池配置优化_spring boot rest 接口集成 spring security(2) – JWT配置
章节
- 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配置相关推荐
- webmvcconfigurer配置跨域_为什么加了 Spring Security 会导致 Spring Boot 跨域失效呢?...
点击上方 IT牧场 ,选择 置顶或者星标 技术干货每日送达 作者:欧阳我去 链接:https://segmentfault.com/a/1190000019485883 作为一个后端开发,我们经常遇到 ...
- Spring Boot (三)集成spring security
项目GitHub地址 : https://github.com/FrameReserve/TrainingBoot Spring Boot (三)集成spring security,标记地址: htt ...
- Spring Boot2 总结(二) Spring Security的基本配置
Spring Boot对Spring Security提供了自动化配置方案,同时这也是在Spring Boot项目中使用Spring Security的优势,因此Spring Security整合 ...
- SpringSecurity权限管理框架系列(六)-Spring Security框架自定义配置类详解(二)之authorizeRequests配置详解
1.预置演示环境 这个演示环境继续沿用 SpringSecurit权限管理框架系列(五)-Spring Security框架自定义配置类详解(一)之formLogin配置详解的环境. 2.自定义配置类 ...
- maven springboot 除去指定的jar包_Spring Boot打包瘦身 Docker 使用全过程 动态配置、日志记录配置...
springBoot打包的时候代码和jar包打包在同一个jar包里面,会导致jar包非常庞大,在不能连接内网的时候调试代码,每次只改动了java代码就需要把所有的jar包一起上传,导致传输文件浪费了很 ...
- dubbo yml配置_Spring boot 的profile功能如何实现多环境配置自动切换
通常服务端应用开发需要经过以下几个流程: 开发 -> 测试 -> RC验证 -> 上线 这就涉及到四个不同的环境,开发环境.测试环境.RC环境以及生产环境,为了避免不同环境之间相互干 ...
- boot spring 获取请求端口浩_Spring精华问答 | 如何集成Spring Boot?
Spring框架是一个开源的Java平台,它提供了非常容易,非常迅速地开发健壮的Java应用程序的全面的基础设施支持.今天就让我们一起来看看关于Spring的精华问答吧. 1 Q:如何在自定义端口上运 ...
- spring Security 重复登录配置无效的问题
关于spring Security重复登录的配置,百度一大堆,我这里就不啰嗦了. 今天碰到 按照网上的配置,但是 感觉配置无效,同一用户还是可以登录,不知道为什么,开始以为是自己配置的又问题.再三确认 ...
- Spring Boot 2.0 利用 Spring Security 实现简单的OAuth2.0认证方式1
0. 前言 之前帐号认证用过自己写的进行匹配,现在要学会使用标准了.准备了解和使用这个OAuth2.0协议. 1. 配置 1.1 配置pom.xml 有些可能会用不到,我把我项目中用到的所有包都贴出来 ...
- Spring Security是什么,以及如何在Spring Boot项目中整合Spring Security并且使用它,下面我们通过一个登录案例简单介绍一下Spring Security。
1.什么是Spring Security? 在了解Spring Security之前,我们是不是应该先思考一个问题,我们自己写的web案例一般都需要先登录,之后登录之后才能访问其他页面,或者说我们不同 ...
最新文章
- 八大深度学习最佳实践
- 利用OpenCV建立视差图像
- html圆圈里面问号,html,css实现问号提示信息
- 记录一下python绘制地图
- python学习 day1 (3月1日)
- DSP中两个延时函数的区别(转)
- Neo4j安装及使用
- 【洛谷P5019+P1969+P3078】道路铺设(暴力模拟/差分)
- CSS 字体加粗,导致布局宽度改变怎么处理?
- RAID环境中增加容量-在线扩容
- Zotero文献管理软件使用指南——入门篇
- warning: array subscript has type ‘char‘
- verilog实现5分频
- 【project】Adruino小型自平衡机器人EVA(+硬件+源代码+3D文件)
- 从零开始搞起 Disney BRDF源码编译(含踩坑经历)
- 项目管理界最经典教材——PMBOK指南,如果现在备考PMP看哪一版?
- SSM整合,非常详细的SSM整合
- 2021年安全员-A证(广西省-2021版)考试及安全员-A证(广西省-2021版)考试试卷
- krait和kryo_各种Java序列化性能比较
- 计算机科学专业和商科专业排名,2017年QS世界大学专业排名权威发布