网络安全的重要性不言而喻,如今早已不是以前随便弄个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 {@Autowiredprivate RestfulAccessDeniedHandler restfulAccessDeniedHandler;@Autowiredprivate RestAuthenticationEntryPoint restAuthenticationEntryPoint;@Autowiredprivate IgnoreUrlsConfig ignoreUrlsConfig;@BeanSecurityFilterChain 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 filterhttpSecurity.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);//添加自定义未授权和未登录结果返回httpSecurity.exceptionHandling().accessDeniedHandler(restfulAccessDeniedHandler).authenticationEntryPoint(restAuthenticationEntryPoint);return httpSecurity.build();}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Beanpublic 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);@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);// 判断是否有tokenif (authHeader != null && authHeader.startsWith(this.tokenHead)) {//提取出来tokenString 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 {@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();}
}
如果用户没有登陆就请求了,我们不应该返回禁止,而是返回未授权的提示,这个编码和禁止差不多,只不过需要实现的接口不一样:
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 {@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();}
}
到这里已经完成及安全模块的配置,不过我们前面写的配置好像和用户角色管理没有关系,因为我们没有实现我们自己的用户管理服务呀,那我们要来实现以下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 {@Autowiredprivate UmsAdminService adminService;@Beanpublic UserDetailsService userDetailsService() {//获取登录用户信息return new UserDetailsService() {@Overridepublic 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;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return this.authorityList.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());}@Overridepublic String getPassword() {return this.password;}@Overridepublic String getUsername() {return this.username;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic 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<>();@Autowiredprivate JwtTokenUtil jwtTokenUtil;@Autowiredprivate PasswordEncoder passwordEncoder;@PostConstructprivate 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());*/}@Overridepublic 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;
// }@Overridepublic 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 {@Autowiredprivate UmsAdminService adminService;@Value("${jwt.tokenHeader}")private String tokenHeader;@Value("${jwt.tokenHead}")private String tokenHead;@ApiOperation(value = "登录以后返回token")@RequestMapping(value = "/login", method = RequestMethod.POST)@ResponseBodypublic 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)@ResponseBodypublic CommonResult<List<UmsResource>> resourceList() {List<UmsResource> resourceList = adminService.getResourceList();return CommonResult.success(resourceList);}
}
然后就是运行,测试看效果。