Web菜鸟教程 - Springboot接入认证授权模块

news2024/9/24 11:30:50

网络安全的重要性不言而喻,如今早已不是以前随便弄个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);
    }
}

然后就是运行,测试看效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/890441.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

指针作为函数参数间接赋值成立的三个条件,例程,内存调用模型

#include <stdio.h> #include <stdlib.h> int config_read(char * filep, char *** p, int * len) { printf(“config file name is :%s\n”, filep); char ** tempP (char**)malloc(sizeof(char *)*10); for(int i 0; i<10; i) {tempP[i] (char *)malloc(2…

LC-相同的树

LC-相同的树 链接&#xff1a;https://leetcode.cn/problems/same-tree/solutions/363636/xiang-tong-de-shu-by-leetcode-solution/ 描述&#xff1a;给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并…

驱动DAY4 字符设备驱动分步注册和ioctl函数点亮LED灯

头文件 #ifndef __HEAD_H__ #define __HEAD_H__ typedef struct{unsigned int MODER;unsigned int OTYPER;unsigned int OSPEEDR;unsigned int PUPDR;unsigned int IDR;unsigned int ODR; }gpio_t; #define PHY_LED1_ADDR 0X50006000 #define PHY_LED2_ADDR 0X50007000 #d…

Next.js - Pages and Layouts

Pages 页面是路由独有的用户界面。你可以通过从 page.js 文件导出组件来定义页面。使用嵌套文件夹定义路由&#xff0c;并使用 page.js 文件公开访问路由。 // app/page.tsx is the UI for the / URL export default function Page() {return <h1>Hello, Home page!<…

python爬虫7:实战1

python爬虫7&#xff1a;实战1 前言 ​ python实现网络爬虫非常简单&#xff0c;只需要掌握一定的基础知识和一定的库使用技巧即可。本系列目标旨在梳理相关知识点&#xff0c;方便以后复习。 申明 ​ 本系列所涉及的代码仅用于个人研究与讨论&#xff0c;并不会对网站产生不好…

QT学习笔记-Linux ARM环境下实现QT程序通过ODBC驱动访问SQLServer数据库

QT学习笔记-Linux ARM环境下实现QT程序通过ODBC驱动访问SQLServer数据库 0、背景1、基本环境2、搭建交叉编译环境3、在交叉编译服务器上交叉编译安装unixODBC3.1 下载unixODBC3.2 交叉编译unixODBC3.2.1 基本编译说明3.2.2 交叉编译说明3.2.3 ./configure -build,-host,-target…

iTOP-RK3399开发板开发板作为客户端- ssh使用

默认使用本地用户为远程登录时的用户&#xff1b;远程连接虚拟机里面的 Ubuntu&#xff08;IP 地址为 192.168.1.20&#xff09;&#xff0c; 如下图所示&#xff1a; ssh 192.168.1.20 以指定用户身份连接至服务器,输入以下命令&#xff1a; 输入 exit 退出 ssh 登录。 更多…

基础设施SIG月度动态:龙蜥大讲堂 - 基础设施系列专题分享火热进行中(7~8 月上旬持续分享),敬请关注!

基础设施 SIG&#xff08;OpenAnolis Infra SIG&#xff09;目标&#xff1a;负责 OpenAnolis 社区基础设施工程平台的建设&#xff0c;包括官网、Bugzilla、Maillist、ABS、ANAS、CI 门禁以及社区 DevOps 相关的研发工程系统。 01 SIG 整体进展 1、龙蜥大讲堂 - 基础设施系…

Vue3和Vue2对比学习之全局 API 应用实例

文章目录 0.前言1.参考文档2.详细说明2.1 全局 API 应用实例 非兼容2.2 一个新的全局 API&#xff1a;createAppconfig.productionTip 移除config.ignoredElements 替换为 config.isCustomElementVue.prototype 替换为 config.globalPropertiesVue.extend 移除类型推断组件继承…

最新年轻人副业赚钱攻略

最近几年&#xff0c;随着互联网的快速发展和社会变革的加速&#xff0c;年轻人副业赚钱的需求也越来越大。无论是为了增加收入、提升个人能力还是实现自我价值&#xff0c;年轻人们都纷纷开始寻找副业赚钱的机会。 那么&#xff0c;如何才能在副业赚钱中脱颖而出呢&#xff1…

软考笔记——10.项目管理

进度管理 进度管理就是采用科学的方法&#xff0c;确定进度目标&#xff0c;编制进度计划和资源供应计划&#xff0c;进行进度控制&#xff0c;在与质量、成本目标协调的基础上&#xff0c;实现工期目标。 具体来说&#xff0c;包括以下过程&#xff1a; (1) 活动定义&#…

Appium-移动端自动测试框架,如何入门?

Appium是一个开源跨平台移动应用自动化测试框架。 既然只是想学习下Appium如何入门&#xff0c;那么我们就直奔主题。文章结构如下&#xff1a; 1、为什么要使用Appium&#xff1f; 2、如何搭建Appium工具环境?(超详细&#xff09; 3、通过demo演示Appium的使用 4、Appium如何…

PD协议受电端芯片是什么?——浅谈快充协议在Hi003/XSP16应用

PD协议的全称是USB Power Delivery协议&#xff0c;该协议是基于USB 3.1中的Type-C接口&#xff0c;所以一般使用Type-C接口的充电器都有PD协议。 目前充电器常见的USB-PD功率为&#xff1a;5V3A&#xff08;15W&#xff09;、9V2A&#xff08;18W&#xff09;、12V3A&#xf…

扩散模型实战(四):从零构建扩散模型

推荐阅读列表&#xff1a; 扩散模型实战&#xff08;一&#xff09;&#xff1a;基本原理介绍 扩散模型实战&#xff08;二&#xff09;&#xff1a;扩散模型的发展 扩散模型实战&#xff08;三&#xff09;&#xff1a;扩散模型的应用 本文以MNIST数据集为例&#xff0c;从…

【Linux】POSIX信号量和基于环形队列的生产消费者模型

目录 写在前面的话 什么是POSIX信号量 POSIX信号量的使用 基于环形队列的生产消费者模型 写在前面的话 本文章主要先介绍POSIX信号量&#xff0c;以及一些接口的使用&#xff0c;然后再编码设计一个基于环形队列的生产消费者模型来使用这些接口。 讲解POSIX信号量时&#x…

阿里云服务器镜像大全_Linux和Windows操作系统清单

阿里云服务器操作系统大全&#xff0c;阿里云提供的镜像均为正版授权&#xff0c;正版镜像可以在云服务器ECS上运行的应用程序提供安全、稳定的运行环境系统&#xff0c;阿里云服务器以公共镜像为例分享阿里云服务器操作系统大全&#xff0c;包括Alibaba Cloud Linux镜像、Linu…

洁净室气流流型分类、检测及相关法规要求概览

洁净室气流形式可以分为单向流或非单向流两种。如果综合利用两种气流&#xff0c;通常叫做混合气流。单向流可以是垂直的或水平的。两种单向流都以最终过滤的送风和回风入口&#xff0c;它们几乎是相对设置&#xff0c;这样才能使气流的流动形式保持尽可能的呈直线状。确认气流…

java全局捕捉异常并返回统一返回值

1. 首先定义全局异常处理类 import org.springframework.validation.BindException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.…

vcsa6.7 vsan超融合,虚拟机突然识别不到加密狗

问题现象&#xff1a; vcsa6.7 vsan超融合&#xff0c;虚拟机突然识别不到加密狗&#xff0c;加密狗直通给虚拟机&#xff0c;打开设备管理器&#xff0c;有未知的usb设备&#xff0c;重启虚拟机&#xff0c;卸载tools&#xff0c;重新安装tools&#xff0c;都不行 问题解决…

面向对象编程(OOP):Python中的抽象与封装

文章目录 &#x1f340;引言&#x1f340; 类与对象&#x1f340;封装&#x1f340;继承&#x1f340;多态&#x1f340;面向对象编程的优势&#x1f340;使用面向对象编程的场景&#x1f340;实例化与构造函数&#x1f340; 成员属性和类属性&#x1f340;魔术方法&#x1f34…