网络安全的重要性不言而喻,如今早已不是以前随便弄个http请求就能爬到数据的时代,而作为一个架构师,网络安全必须在产品开发之初就考虑好。因为在产品开发的后期,一方面是客户增多,压力变大,可供利用的时间也会变少,另一方面,随着时间的推移,项目越发庞大,这个时候想要在开发继续推进的同时来调整架构,带来的影响不可谓不大。
同时,对于多用户系统来说,必然涉及到用户角色管理,不同的用户不可能具有相同的权限,而如果要通过硬编码来实现,那工作量可就大了,而且配置起来也极其不灵活,二通过架构方式来编写这些框架代码,那也是不小的工程量,而且对架构师水平要求也不低。
所幸Springboot就自带了安全模块,我们可以在前期设计的时候很轻松的引入这些模块。如果这些模块让我们自己来写一遍,自然是很痛苦的,更主要的是,你写完了不一定有他的好,Web开发最好的地方在于他有太多的脚手架可直接使用。在这里我们需要用到的就是SpringSecurity及JWT。
JWT
JWT是JSON WEB TOKEN的缩写,它是基于 RFC 7519 标准定义的一种可以安全传输的的JSON对象,由于使用了数字签名,所以是可信任和安全的。
JWT的组成
我们先来看一个JWT加密后的token:
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImNyZWF0ZWQiOjE1NTY3NzkxMjUzMDksImV4cCI6MTU1NzM4MzkyNX0.d-iki0193X0bBOETf2UN3r3PotNIEAV7mzIxxeI5IxFyzzkOZxS0PGfF_SK6wxCv2K8S0cZjMkv6b5bCqc0VBw
只是看这个字符串会觉得一脸懵逼,它实际上是遗传加密过的字符串,我们可以在jwt.io/解析出它的原文:
在右边的解析中给出了他内容解析说明,它是由三部分构成:
JWT token的格式:header.payload.signature
- header中用于存放签名的生成算法
{"alg": "HS512"}
- payload中用于存放用户名、token的生成时间和过期时间
{"sub":"admin","created":1489079981393,"exp":1489684781}
- signature为以header和payload生成的签名,一旦header和payload被篡改,验证将失败
//secret为加密算法的密钥
String signature = HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
既然jwt是可以被解析的,那么它到底解决了什么问题呢?我们一般设计授权模块,就是做一个加密的token,然后让客户端在每次请求的时候都带上这个token,我们会对token解析并校验是否合法,不合法就会拒绝服务,jwt健康相当于把这部分功能给我们做了:
- 用户调用登录接口,登录成功后获取到JWT的token;
- 之后用户每次调用接口都在http的header中添加一个叫Authorization的头,值为JWT的token;
- 后台程序通过对Authorization头中信息的解码及数字签名校验来获取其中的用户信息,从而实现认证和授权。
环境
引入安全模块主要用到两个模块,一个是springboot自带的security模块,另一个是jwt模块。在实际项目实战用,为了不重复造轮子,还需要应用hutool这个三方依赖。这个依赖带了一些工具类方法,有点像Android的Xutil可以为我们开发节省很多时间,在这里主要是用来做加解密。如果你有其他更合适的只要能达到效果就行。
依赖
在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>
然后我们需要一些功能性的工具,根据我们的业务我们需要三个功能:
- 根据用户信息生成对应的token,generateToken(UserDetails userDetails)
- 用户用token请求后我们还需要解析这个token,因此需要一个解析的方法getUserNameFromToken(String token)
- 解析token后需要判断是否有效,这里主要是做通用有效性判断:validateToken(String token, UserDetails userDetails)
好了,我们把这个实现下:
package org.lange.mall.common.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* JwtToken生成的工具类
* JWT token的格式:header.payload.signature
* header的格式(算法、token的类型):
* {"alg": "HS512","typ": "JWT"}
* payload的格式(用户名、创建时间、生成时间):
* {"sub":"wang","created":1489079981393,"exp":1489684781}
* signature的生成算法:
* HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
*/
@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);
}
}
上面用到了@Component注解,这个注解和@Service,@Controller使用类似,都是在需要使用的类中通过@Autowired连接就能自动使用。这其实是SpringBoot的Bean管理机制,在组件的使用中,根本不需要去new对象。
配置SpringSecurity
和前面我们了解的Springboot的Mybatis,Swagger一样,使用SpringSecurity需要添加配置类自动化配置。这里需要用到三个额外的注解:
@Configuration
@Autowired
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Bean
前面两个我们前面用过了。EnableWebSecurity作为Springboot自带的一个注解,他的作用就是让项目运行的时候启动安全模块。而@EnableGlobalMethodSecurity,同时这个注解为我们提供了prePostEnabled 、securedEnabled 和 jsr250Enabled 三种不同的机制来实现同一种功能。
@Autowired不仅仅可以用在对象实例化上给成员变量的使用提供getset方法,还可以注解在方法上,而@Bean如果注解在一个返回对象的方法上,调用的时候可以直接使用该对象而不用调用方法来获取对象。
对于SpringSecurity的配置我们需要创建一个配置类SecurityConfig.java
package org.lange.mall.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* SpringSecurity的配置
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Autowired
private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
@Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
@Autowired
private IgnoreUrlsConfig ignoreUrlsConfig;
@Bean
SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity
.authorizeRequests();
//不需要保护的资源路径允许访问
for (String url : ignoreUrlsConfig.getUrls()) {
registry.antMatchers(url).permitAll();
}
//允许跨域请求的OPTIONS请求
registry.antMatchers(HttpMethod.OPTIONS)
.permitAll();
httpSecurity.csrf()// 由于使用的是JWT,我们这里不需要csrf
.disable()
.sessionManagement()// 基于token,所以不需要session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.anyRequest()// 除上面外的所有请求全部需要鉴权认证
.authenticated();
// 禁用缓存
httpSecurity.headers().cacheControl();
// 添加JWT filter
httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
//添加自定义未授权和未登录结果返回
httpSecurity.exceptionHandling()
.accessDeniedHandler(restfulAccessDeniedHandler)
.authenticationEntryPoint(restAuthenticationEntryPoint);
return httpSecurity.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {
return new JwtAuthenticationTokenFilter();
}
}
在上面的配置类里面,最重要的两个方法:filterChain和passwordEncoder,这两个是提供给系统调用的。一个是用来创建拦截器配置,这个拦截器决定了请求放行规则,这个怎么理解呢?比如某些分享链接,或者用户还没有登陆的时候,那他是什么权限也没有的。如果不给放行,那连入口都没有,谈何使用。这就是放行的作用,同时,一些外部的Api调用,也需要在这里配置。
把这个说明白那一串代码就很好理解了。我们要放行某些资源路径,要配置一些跨域请求,然后这里还禁用了缓存,添加了一个token过滤器,用来过滤不正常的请求。然后就是配置了授权不通过的返回,大概就是这些了。这里资源路劲比较多,所以单独放在类IgnoreUrlsConfig
中,为了演示方便,这个类中没啥东西:
package org.lange.mall.config.security;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "secure.ignored")
public class IgnoreUrlsConfig {
private List<String> urls = new ArrayList<>();
}
然后我们来看下授权过滤器要怎么写,JwtAuthenticationTokenFilter.java
package org.lange.mall.component;
import org.lange.mall.common.util.JwtTokenUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {//继承了每次请求一次的过滤器,还有其他的过滤器可以使用
private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Value("${jwt.tokenHeader}")
private String tokenHeader;
@Value("${jwt.tokenHead}")
private String tokenHead;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
// 拿到请求头,因为验证信息都放在通用请求头里面的
String authHeader = request.getHeader(this.tokenHeader);
// 判断是否有token
if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
//提取出来token
String authToken = authHeader.substring(this.tokenHead.length());// The part after "Bearer "
// token中包含username,我们提取出来
String username = jwtTokenUtil.getUserNameFromToken(authToken);
LOGGER.info("checking username:{}", username);
// 能提取到username 并且开了安全验证就继续
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// 通过这个username在service里面查询用户详细
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
// 判断token数据和用户详情是否对的上切token没有过期
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);
}
}
如果没有通过验证,那我们要禁止他访问,返回通用禁止响应,这个很好理解。
package org.lange.mall.component;
import cn.hutool.json.JSONUtil;
import org.lange.mall.common.api.CommonResult;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 当访问接口没有权限时,自定义的返回结果
* 这里是通用禁止响应
*/
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler {
@Override
public 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();
}
}
如果用户没有登陆就请求了,我们不应该返回禁止,而是返回未授权的提示,这个编码和禁止差不多,只不过需要实现的接口不一样:
package org.lange.mall.component;
import cn.hutool.json.JSONUtil;
import org.lange.mall.common.api.CommonResult;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 当未登录或者token失效访问接口时,自定义的返回结果
*/
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public 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();
}
}
到这里已经完成及安全模块的配置,不过我们前面写的配置好像和用户角色管理没有关系,因为我们没有实现我们自己的用户管理服务呀,那我们要来实现以下MallSecurityConfig.java:
package org.lange.mall.config.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
/**
* 自定义配置,用于配置如何获取用户信息
*/
@Configuration
public class MallSecurityConfig {
@Autowired
private UmsAdminService adminService;
@Bean
public UserDetailsService userDetailsService() {
//获取登录用户信息
return new UserDetailsService() {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
AdminUserDetails admin = adminService.getAdminByUsername(username);
if (admin != null) {
return admin;
}
throw new UsernameNotFoundException("用户名或密码错误");
}
};
}
}
顺便实现对应的bean和service,这个bean要实现Springboot的UserDetails,不然会找不到。我们后续是可以扩展这个用户信息类的,这里为了掩饰就不扩展了。
package org.lange.mall.data;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
/**
* SpringSecurity用户信息封装类
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Builder
public class AdminUserDetails implements UserDetails {
private String username;
private String password;
private List<String> authorityList;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorityList.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
然后就是对应的service,面向接口编程是个好习惯,有利于规范化。UmsAdminService.java:
package org.lange.mall.service.impl;
import cn.hutool.core.collection.CollUtil;
import lombok.extern.slf4j.Slf4j;
import org.lange.mall.common.util.JwtTokenUtil;
import org.lange.mall.data.AdminUserDetails;
import org.lange.mall.service.UmsAdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service
public class UmsAdminServiceImpl implements UmsAdminService {
/**
* 存放默认用户信息
*/
private List<AdminUserDetails> adminUserDetailsList = new ArrayList<>();
// 存放默认资源信息
//private List<UmsResource> resourceList = new ArrayList<>();
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private PasswordEncoder passwordEncoder;
@PostConstruct
private void init(){
adminUserDetailsList.add(AdminUserDetails.builder()
.username("admin")
.password(passwordEncoder.encode("123456"))
.authorityList(CollUtil.toList("brand:create","brand:update","brand:delete","brand:list","brand:listAll"))
.build());
adminUserDetailsList.add(AdminUserDetails.builder()
.username("macro")
.password(passwordEncoder.encode("123456"))
.authorityList(CollUtil.toList("brand:listAll"))
.build());
/*resourceList.add(UmsResource.builder()
.id(1L)
.name("brand:create")
.url("/brand/create")
.build());
resourceList.add(UmsResource.builder()
.id(2L)
.name("brand:update")
.url("/brand/update/**")
.build());
resourceList.add(UmsResource.builder()
.id(3L)
.name("brand:delete")
.url("/brand/delete/**")
.build());
resourceList.add(UmsResource.builder()
.id(4L)
.name("brand:list")
.url("/brand/list")
.build());
resourceList.add(UmsResource.builder()
.id(5L)
.name("brand:listAll")
.url("/brand/listAll")
.build());*/
}
@Override
public AdminUserDetails getAdminByUsername(String username) {
List<AdminUserDetails> findList = adminUserDetailsList.stream().filter(item -> item.getUsername().equals(username)).collect(Collectors.toList());
if(CollUtil.isNotEmpty(findList)){
return findList.get(0);
}
return null;
}
// @Override
// public List<UmsResource> getResourceList() {
// return resourceList;
// }
@Override
public String login(String username, String password) {
String token = null;
try {
UserDetails userDetails = getAdminByUsername(username);
if(userDetails==null){
return token;
}
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) {
log.warn("登录异常:{}", e.getMessage());
}
return token;
}
}
到这里业务相关的鉴权我们写完了,但是我们还没有接入到接口中,也就是用户要从哪里请求我们要定义好。我们来实现以下登录接口,通常的登录逻辑是用户拿用户名和密码来登录,登录成功我们返回token,这个时候用户就能用这个token去请求他想要的资源了。如果用户没有登陆就去请求,就会提示未登录,现在我们来实现下。
先定义接口控制器UmsAdminController.java,就两个方法,一个登录,一个请求资源列表。在此之前我们把资源的这个类定义一下:
package org.lange.mall.data;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* 后台资源表
*/
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value="UmsResource对象", description="后台资源表")
@Builder
public class UmsResource{
private Long id;
@ApiModelProperty(value = "创建时间")
private Date createTime;
@ApiModelProperty(value = "资源名称")
private String name;
@ApiModelProperty(value = "资源URL")
private String url;
@ApiModelProperty(value = "描述")
private String description;
@ApiModelProperty(value = "资源分类ID")
private Long categoryId;
}
然后要把service中获取资源列表的方法放开一下,前面被我注释了,UmsAdminController.java
package org.lange.mall.controller;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.lange.mall.common.api.CommonResult;
import org.lange.mall.data.UmsResource;
import org.lange.mall.service.UmsAdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Controller
@Api(tags = "UmsAdminController")
@Tag(name = "UmsAdminController", description = "后台用户管理")
@RequestMapping("/admin")
public class UmsAdminController {
@Autowired
private UmsAdminService adminService;
@Value("${jwt.tokenHeader}")
private String tokenHeader;
@Value("${jwt.tokenHead}")
private String tokenHead;
@ApiOperation(value = "登录以后返回token")
@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody
public CommonResult login(@RequestParam String username, @RequestParam String password) {
String token = adminService.login(username, password);
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(value = "请求资源列表")
@RequestMapping(value = "/resourceList", method = RequestMethod.POST)
@ResponseBody
public CommonResult<List<UmsResource>> resourceList() {
List<UmsResource> resourceList = adminService.getResourceList();
return CommonResult.success(resourceList);
}
}
然后就是运行,测试看效果。