整合SpringSecurity和JWT实现认证和授权

环境搭建

  • SpringSecurity
  • JWT
  • Hutool

项目使用表说明

ums_admin:后台用户表
ums_role:后台用户角色表
ums_permission:后台用户权限表
ums_admin_role_relation:后台用户和角色关系表,用户与角色是多对多关系
ums_role_permission_relation:后台用户角色和权限关系表,角色与权限是多对多关系
ums_admin_permission_relation:后台用户和权限关系表(除角色中定义的权限以外的加减权限),加权限是指用户比角色多出的权限,减权限是指用户比角色少的权限

整合SpringSecurity及JWT

在pom.xml中添加项目依赖

<!--SpringSecurity依赖配置-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--Hutool Java工具包-->
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>4.5.7</version>
</dependency>
<!--JWT(Json Web Token)登录支持-->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.0</version>
</dependency>

添加JWT token的工具类

用于生成和解析JWT token 的工具类

相关方法说明:

  • generateToken(UserDetails userDetails) :用于根据登录用户信息生成token
  • getUserNameFromToken(String token):从token中获取登录用户的信息
  • validateToken(String token, UserDetails userDetails):判断token是否还有效
/*** JwtToken生成的工具类*/
@Component
public class JwtTokenUtil {private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class);private static final String CLAIM_KEY_USERNAME = "sub";private static final String CLAIM_KEY_CREATED = "created";@Value("${jwt.secret}")private String secret;@Value("${jwt.expiration}")private Long expiration;/*** 根据负责生成JWT的token*/private String generateToken(Map<String, Object> claims) {return Jwts.builder().setClaims(claims).setExpiration(generateExpirationDate()).signWith(SignatureAlgorithm.HS512, secret).compact();}/*** 从token中获取JWT中的负载*/private Claims getClaimsFromToken(String token) {Claims claims = null;try {claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();} catch (Exception e) {LOGGER.info("JWT格式验证失败:{}",token);}return claims;}/*** 生成token的过期时间*/private Date generateExpirationDate() {return new Date(System.currentTimeMillis() + expiration * 1000);}/*** 从token中获取登录用户名*/public String getUserNameFromToken(String token) {String username;try {Claims claims = getClaimsFromToken(token);username =  claims.getSubject();} catch (Exception e) {username = null;}return username;}/*** 验证token是否还有效** @param token       客户端传入的token* @param userDetails 从数据库中查询出来的用户信息*/public boolean validateToken(String token, UserDetails userDetails) {String username = getUserNameFromToken(token);return username.equals(userDetails.getUsername()) && !isTokenExpired(token);}/*** 判断token是否已经失效*/private boolean isTokenExpired(String token) {Date expiredDate = getExpiredDateFromToken(token);return expiredDate.before(new Date());}/*** 从token中获取过期时间*/private Date getExpiredDateFromToken(String token) {Claims claims = getClaimsFromToken(token);return claims.getExpiration();}/*** 根据用户信息生成token*/public String generateToken(UserDetails userDetails) {Map<String, Object> claims = new HashMap<>();claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());claims.put(CLAIM_KEY_CREATED, new Date());return generateToken(claims);}/*** 判断token是否可以被刷新*/public boolean canRefresh(String token) {return !isTokenExpired(token);}/*** 刷新token*/public String refreshToken(String token) {Claims claims = getClaimsFromToken(token);claims.put(CLAIM_KEY_CREATED, new Date());return generateToken(claims);}
}

添加SpringSecurity的配置类

/*** SpringSecurity的配置*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UmsAdminService adminService;@Autowiredprivate RestfulAccessDeniedHandler restfulAccessDeniedHandler;@Autowiredprivate RestAuthenticationEntryPoint restAuthenticationEntryPoint;@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception {httpSecurity.csrf()// 由于使用的是JWT,我们这里不需要csrf.disable().sessionManagement()// 基于token,所以不需要session.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests().antMatchers(HttpMethod.GET, // 允许对于网站静态资源的无授权访问"/","/*.html","/favicon.ico","/**/*.html","/**/*.css","/**/*.js","/swagger-resources/**","/v2/api-docs/**").permitAll().antMatchers("/admin/login", "/admin/register")// 对登录注册要允许匿名访问.permitAll().antMatchers(HttpMethod.OPTIONS)//跨域请求会先进行一次options请求.permitAll()
//                .antMatchers("/**")//测试时全部运行访问
//                .permitAll().anyRequest()// 除上面外的所有请求全部需要鉴权认证.authenticated();// 禁用缓存httpSecurity.headers().cacheControl();// 添加JWT filterhttpSecurity.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);//添加自定义未授权和未登录结果返回httpSecurity.exceptionHandling().accessDeniedHandler(restfulAccessDeniedHandler).authenticationEntryPoint(restAuthenticationEntryPoint);}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Beanpublic UserDetailsService userDetailsService() {//获取登录用户信息return username -> {UmsAdmin admin = adminService.getAdminByUsername(username);if (admin != null) {List<UmsPermission> permissionList = adminService.getPermissionList(admin.getId());return new AdminUserDetails(admin,permissionList);}throw new UsernameNotFoundException("用户名或密码错误");};}@Beanpublic JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){return new JwtAuthenticationTokenFilter();}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}}

相关依赖和方法说明

  • configure(HttpSecurity httpSecurity):用于配置需要拦截的url路径、jwt过滤器及出异常后的处理器;
  • configure(AuthenticationManagerBuilder auth):用于配置UserDetailsService及PasswordEncoder;
  • RestfulAccessDeniedHandler:当用户没有访问权限时的处理器,用于返回JSON格式的处理结果;
  • RestAuthenticationEntryPoint:当未登录或token失效时,返回JSON格式的结果;
  • UserDetailsService:SpringSecurity定义的核心接口,用于根据用户名获取用户信息,需要自行实现;
  • UserDetails:SpringSecurity定义用于封装用户信息的类(主要是用户信息和权限),需要自行实现;
  • PasswordEncoder:SpringSecurity定义的用于对密码进行编码及比对的接口,目前使用的是BCryptPasswordEncoder;
  • JwtAuthenticationTokenFilter:在用户名和密码校验前添加的过滤器,如果有jwt的token,会自行根据token信息进行登录。

添加RestfulAccessDeniedHandler

/*** 当访问接口没有权限时,自定义的返回结果*/
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler{@Overridepublic void handle(HttpServletRequest request,HttpServletResponse response,AccessDeniedException e) throws IOException, ServletException {response.setCharacterEncoding("UTF-8");response.setContentType("application/json");response.getWriter().println(JSONUtil.parse(CommonResult.forbidden(e.getMessage())));response.getWriter().flush();}
}

添加RestAuthenticationEntryPoint

/*** 当未登录或者token失效访问接口时,自定义的返回结果*/
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {response.setCharacterEncoding("UTF-8");response.setContentType("application/json");response.getWriter().println(JSONUtil.parse(CommonResult.unauthorized(authException.getMessage())));response.getWriter().flush();}
}

添加AdminUserDetails

/*** SpringSecurity需要的用户详情*/
public class AdminUserDetails implements UserDetails {private UmsAdmin umsAdmin;private List<UmsPermission> permissionList;public AdminUserDetails(UmsAdmin umsAdmin, List<UmsPermission> permissionList) {this.umsAdmin = umsAdmin;this.permissionList = permissionList;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {//返回当前用户的权限return permissionList.stream().filter(permission -> permission.getValue()!=null).map(permission ->new SimpleGrantedAuthority(permission.getValue())).collect(Collectors.toList());}@Overridepublic String getPassword() {return umsAdmin.getPassword();}@Overridepublic String getUsername() {return umsAdmin.getUsername();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return umsAdmin.getStatus().equals(1);}
}

添加JwtAuthenticationTokenFilter

在用户名和密码校验前添加的过滤器,如果请求中有jwt的token且有效,会取出token中的用户名,然后调用SpringSecurity的API进行登录操作。

/*** JWT登录授权过滤器*/
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate JwtTokenUtil jwtTokenUtil;@Value("${jwt.tokenHeader}")private String tokenHeader;@Value("${jwt.tokenHead}")private String tokenHead;@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain chain) throws ServletException, IOException {String authHeader = request.getHeader(this.tokenHeader);if (authHeader != null && authHeader.startsWith(this.tokenHead)) {String authToken = authHeader.substring(this.tokenHead.length());// The part after "Bearer "String username = jwtTokenUtil.getUserNameFromToken(authToken);LOGGER.info("checking username:{}", username);if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);if (jwtTokenUtil.validateToken(authToken, userDetails)) {UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));LOGGER.info("authenticated user:{}", username);SecurityContextHolder.getContext().setAuthentication(authentication);}}}chain.doFilter(request, response);}
}

登录注册功能实现

添加UmsAdminController类

实现了后台用户登录、注册及获取权限的接口

/*** 后台用户管理*/
@Controller
@Api(tags = "UmsAdminController", description = "后台用户管理")
@RequestMapping("/admin")
public class UmsAdminController {@Autowiredprivate UmsAdminService adminService;@Value("${jwt.tokenHeader}")private String tokenHeader;@Value("${jwt.tokenHead}")private String tokenHead;@ApiOperation(value = "用户注册")@RequestMapping(value = "/register", method = RequestMethod.POST)@ResponseBodypublic CommonResult<UmsAdmin> register(@RequestBody UmsAdmin umsAdminParam, BindingResult result) {UmsAdmin umsAdmin = adminService.register(umsAdminParam);if (umsAdmin == null) {CommonResult.failed();}return CommonResult.success(umsAdmin);}@ApiOperation(value = "登录以后返回token")@RequestMapping(value = "/login", method = RequestMethod.POST)@ResponseBodypublic CommonResult login(@RequestBody UmsAdminLoginParam umsAdminLoginParam, BindingResult result) {String token = adminService.login(umsAdminLoginParam.getUsername(), umsAdminLoginParam.getPassword());if (token == null) {return CommonResult.validateFailed("用户名或密码错误");}Map<String, String> tokenMap = new HashMap<>();tokenMap.put("token", token);tokenMap.put("tokenHead", tokenHead);return CommonResult.success(tokenMap);}@ApiOperation("获取用户所有权限(包括+-权限)")@RequestMapping(value = "/permission/{adminId}", method = RequestMethod.GET)@ResponseBodypublic CommonResult<List<UmsPermission>> getPermissionList(@PathVariable Long adminId) {List<UmsPermission> permissionList = adminService.getPermissionList(adminId);return CommonResult.success(permissionList);}
}

添加UmsAdminService接口

/*** 后台管理员Service*/
public interface UmsAdminService {/*** 根据用户名获取后台管理员*/UmsAdmin getAdminByUsername(String username);/*** 注册功能*/UmsAdmin register(UmsAdmin umsAdminParam);/*** 登录功能* @param username 用户名* @param password 密码* @return 生成的JWT的token*/String login(String username, String password);/*** 获取用户所有权限(包括角色权限和+-权限)*/List<UmsPermission> getPermissionList(Long adminId);
}

添加UmsAdminServiceImpl类

/*** UmsAdminService实现类*/
@Service
public class UmsAdminServiceImpl implements UmsAdminService {private static final Logger LOGGER = LoggerFactory.getLogger(UmsAdminServiceImpl.class);@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate JwtTokenUtil jwtTokenUtil;@Autowiredprivate PasswordEncoder passwordEncoder;@Value("${jwt.tokenHead}")private String tokenHead;@Autowiredprivate UmsAdminMapper adminMapper;@Autowiredprivate UmsAdminRoleRelationDao adminRoleRelationDao;@Overridepublic UmsAdmin getAdminByUsername(String username) {UmsAdminExample example = new UmsAdminExample();example.createCriteria().andUsernameEqualTo(username);List<UmsAdmin> adminList = adminMapper.selectByExample(example);if (adminList != null && adminList.size() > 0) {return adminList.get(0);}return null;}@Overridepublic UmsAdmin register(UmsAdmin umsAdminParam) {UmsAdmin umsAdmin = new UmsAdmin();BeanUtils.copyProperties(umsAdminParam, umsAdmin);umsAdmin.setCreateTime(new Date());umsAdmin.setStatus(1);//查询是否有相同用户名的用户UmsAdminExample example = new UmsAdminExample();example.createCriteria().andUsernameEqualTo(umsAdmin.getUsername());List<UmsAdmin> umsAdminList = adminMapper.selectByExample(example);if (umsAdminList.size() > 0) {return null;}//将密码进行加密操作String encodePassword = passwordEncoder.encode(umsAdmin.getPassword());umsAdmin.setPassword(encodePassword);adminMapper.insert(umsAdmin);return umsAdmin;}@Overridepublic String login(String username, String password) {String token = null;try {UserDetails userDetails = userDetailsService.loadUserByUsername(username);if (!passwordEncoder.matches(password, userDetails.getPassword())) {throw new BadCredentialsException("密码不正确");}UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());SecurityContextHolder.getContext().setAuthentication(authentication);token = jwtTokenUtil.generateToken(userDetails);} catch (AuthenticationException e) {LOGGER.warn("登录异常:{}", e.getMessage());}return token;}@Overridepublic List<UmsPermission> getPermissionList(Long adminId) {return adminRoleRelationDao.getPermissionList(adminId);}
}

修改swagger的配置

通过修改配置实现调用接口自带Authorization头,这样就可以访问需要登录的接口了。

/*** Swagger2API文档的配置*/
@Configuration
@EnableSwagger2
public class Swagger2Config {@Beanpublic Docket createRestApi(){return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()//为当前包下controller生成API文档.apis(RequestHandlerSelectors.basePackage("com.macro.mall.tiny.controller")).paths(PathSelectors.any()).build()//添加登录认证.securitySchemes(securitySchemes()).securityContexts(securityContexts());}private ApiInfo apiInfo() {return new ApiInfoBuilder().title("SwaggerUI演示").description("mall-tiny").contact("macro").version("1.0").build();}private List<ApiKey> securitySchemes() {//设置请求头信息List<ApiKey> result = new ArrayList<>();ApiKey apiKey = new ApiKey("Authorization", "Authorization", "header");result.add(apiKey);return result;}private List<SecurityContext> securityContexts() {//设置需要登录认证的路径List<SecurityContext> result = new ArrayList<>();result.add(getContextByPath("/brand/.*"));return result;}private SecurityContext getContextByPath(String pathRegex){return SecurityContext.builder().securityReferences(defaultAuth()).forPaths(PathSelectors.regex(pathRegex)).build();}private List<SecurityReference> defaultAuth() {List<SecurityReference> result = new ArrayList<>();AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];authorizationScopes[0] = authorizationScope;result.add(new SecurityReference("Authorization", authorizationScopes));return result;}
}

给PmsBrandController接口中的方法添加访问权限

  • 给查询接口添加pms:brand:read权限
  • 给修改接口添加pms:brand:update权限
  • 给删除接口添加pms:brand:delete权限
  • 给添加接口添加pms:brand:create权限
    例如:
@PreAuthorize("hasAuthority('pms:brand:read')")
public CommonResult<List<PmsBrand>> getBrandList() {return CommonResult.success(brandService.listAllBrand());
}

商城项目(三)整合SpringSecurity和JWT实现认证和授权相关推荐

  1. GitHub开源项目学习 电商系统Mall (四) mall整合SpringSecurity和JWT实现认证和授权(一)

    mall整合SpringSecurity和JWT实现认证和授权(一) https://github.com/macrozheng/mall 跳过了官方Learning中较简单的Swagger-UI的实 ...

  2. GitHub开源项目学习 电商系统Mall (五) mall整合SpringSecurity和JWT实现认证和授权(二)

    mall整合SpringSecurity和JWT实现认证和授权(二) https://github.com/macrozheng/mall 登录注册功能实现 UmsAdminController类 实 ...

  3. 解决测试失败问题:mall商城项目》mall整合SpringSecurity和JWT实现认证和授权(二)》改用其他有权限的帐号登录

    目录 原因 修改办法 原因 权限值不对 修改办法 1.打开数据库中的ums_admin表 2.复制id=1那一行的password,如下: 3.替换id=6那一行中的password,点击√,如下: ...

  4. 5.1基于JWT的认证和授权「深入浅出ASP.NET Core系列」

    原文:5.1基于JWT的认证和授权「深入浅出ASP.NET Core系列」 希望给你3-5分钟的碎片化学习,可能是坐地铁.等公交,积少成多,水滴石穿,码字辛苦,如果你吃了蛋觉得味道不错,希望点个赞,谢 ...

  5. IDEA创建一个springboot项目(三)整合swagge接口测试框架

    我是在上一篇的demo基础上增加的,上一篇地址:springboot项目(二)整合TKMytis框架 一:认识Swagger Swagger 是一个规范和完整的框架,用于生成.描述.调用和可视化 RE ...

  6. SpringBoot+SpringSecurity+JWT实现认证和授权

    SprinBoot 系列文章: Spring Boot入门之Hello Spring Boot SpringBoot 配置多个JdbcTemplate SpringBoot 整合Mybatis CAS ...

  7. Springboot整合shiro基于url身份认证和授权认证

    你还不会shiro吗? 前奏 shiro核心配置文件(rolesFilter可选). 身份认证 多表登录源如何操作? 授权管理 如何解决界面多角色/资源问题 访问效果 权限管理在日常开发中很重要,所以 ...

  8. ASP.NET Core WebAPI中使用JWT Bearer认证和授权

    为什么是 JWT Bearer ASP.NET Core 在 Microsoft.AspNetCore.Authentication 下实现了一系列认证, 包含 Cookie, JwtBearer,  ...

  9. 实践剖析.NET Core如何支持Cookie滑动过期和JWT混合认证、授权

    [导读]为防止JWT Token被窃取,我们将Token置于Cookie中,但若与第三方对接,调用我方接口进行认证.授权此时仍需将Token置于请求头,通过实践并联系理论,我们继续开始整活 首先我们实 ...

最新文章

  1. 配置文件总结(机房重构知识点总结)
  2. springmvc学习资料整理
  3. south eastern china at a scope of 100km
  4. 【数据结构与算法】常用算法
  5. 怎样在Ubuntu 14.04中搭建gitolite git服务器
  6. can1--can初探
  7. 学生信息管理信息系统--添加用户
  8. 「2019冬令营提高组」送分题
  9. Mail_Android_Video_SW_DDK_Intergration_Guide_And_Codec_User_Manual中文翻译【chapter2】
  10. PyTorch固定随机数种子
  11. 牌匾设计软件测试自学,回弹检测测点
  12. 一般试卷的纸张大小是多少_试卷字体多大合适 标准试卷的字体大小
  13. 高保真扬声器系统的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告的全球与中国市场
  14. Dram学习笔记(1) Dram相关基础知识
  15. 仙人掌真的会防辐射吗
  16. 怎样将CAD里面画好的图纸转换到WORD文档里面去
  17. Atcoder 284题解
  18. 探究工业机器人、自动化、PLC这三者到底是什么关系?
  19. info1110辅导quiz1
  20. SpringCloud使用@Aspect面向切面处理Web请求日志

热门文章

  1. 应届生工资怎么谈? 今天告诉你个概念叫整体报酬
  2. 校园铃声系统 v6.2 官方
  3. 9.客户端检测(2)
  4. mcem r语言代码_R语言面向对象编程:S3和R6
  5. 在Window/Linux跨平台开源环境开发8051应用
  6. Word 2007-破解只能浏览而不能编辑的文档
  7. 输入年月日,判断这一天是该年的第几天,距离该年结束还有多少天
  8. target_compile_features specified unknown feature “cxx_std_14“ for target
  9. 杭电hduoj 2041 超级楼梯
  10. 创业板股票提示没有交易权限,怎么开通创业板股票交易权限?