Springboot 整合swagger、springsecurity、jjwt、实现前后端分离架构的权限认证搭建。

Springboot 整合swagger+spring security+jjwt,实现前后端分离架构的权限认证。

一、写本文的目的性

1.1、网上有很多关于springsecurity、整合jjwt的相关例子,我前段时间因为个人原因,需要整合,但是看了网上的例子,要不太过于复杂,要么前后端耦合度太高,要么没有什么ruan用(但是我还是找到了某位猿猿的分享,得到了启发)

二、需要做的准备

2.1、没学过springboot、springsecurity的去学一下

  • 这里有一份springsecurity的中文文档,内容比较多,但实际需要的核心部分不多。spring security中文文档
  • springboot的学习自己去网上找一下吧

2.1、关于为什么要选用jjwt而不是选择其他

  • 因为jjwt支持的东西很多,使用它的人也很多很多、大众的眼光是闪亮的。拿这么一个比喻,宫颈癌疫苗有免费九价的和免费二价的,你会选择二价吗。jwt官网
  • 在这里插入图片描述

三、整合的大概流程和模块

3、这一部分很重要,有助于理解。

相关配置和工具类

1、jwt的工具类:用于生产,解析token,设置token时间等一系列的操作接口。
2、security配置类:用于配置角色(权限)、放行api,登录成功处理器,配置登录失败处理器,请求失败处理器,放行swagger接口配置等等。
3、重写UserDetailsService(或者说是重写其loadUserByUsername方法也可以)
4、编写自定义响应类:这个如果不是实际开发中,是可有可无的。

其他处理器和拦截器

1、OncePerRequestFilter拦截器:拦截客户端请求过来的一切请求,用于token校验、等操作。
2、登录成功处理器:登录成功后,返回生成的token到客户端。
3、登录失败处理器:登录失败之后,返回错误信息给前端。
4、其他自定义resfulController:后续再进行对应的描述。

四、代码说明

4.1、引入相关依赖

其实这篇文章挺长的,这个依赖我都有点不想贴出来了。

        </dependency>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-security</artifactId>
                </dependency>
        <dependency>
        </dependency>
                <dependency>
                    <groupId>org.springframework.security</groupId>
                    <artifactId>spring-security-test</artifactId>
                    <scope>test</scope>
                </dependency>
        <dependency>
         <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>
        <dependency>
            <groupId>com.spring4all</groupId>
            <artifactId>swagger-spring-boot-starter</artifactId>
            <version>1.9.0.RELEASE</version>
        </dependency>

说明:除此几个依赖还有其他依赖我就不一一列出来了,比如springboot 启动依赖,等等,还有一点就是我用的swagger是com.spring4all,下的swagger,使用的方式其实一样。至于为什么使用这个,就是因为我懒。懒得配置那么多,喜欢他,喜欢一样东西不需要太多理由。

4.2工具类

JwtTokenUtils.java 用于创建和校验token,设置秘钥等相关操作,具体操作在代码里面了。

package com.dly.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.*;

public class JwtTokenUtils {

    public static final String TOKEN_HEADER = "Authorization";

    public static final String TOKEN_PREFIX = "Bearer ";
    /**
     * 密钥key 
     */
    private static final String SECRET = "jwtsecurit";

    /**
     * JWT的发行人
     */
    private static final String ISS = "nianlan";

    /**
     * 自定义用户信息
     */
    private static final String ROLE_CLAIMS = "role";

    /**
     * 过期时间是3600秒,既是1个小时
     */
    public static final long EXPIRATION = 3600L * 1000;
    /**
     * 选择了记住我之后的过期时间为7天
     */
    public static final long EXPIRATION_REMEMBER = 604800L * 1000;

    /**
     * 创建token
     *
     *
     *       登录名`在这里插入代码片`
     *
     *            用户角色信息
     * @param isRememberMe
     *            是否记住我
     * @return
     */
    public static String createToken(UserDetails details, boolean isRememberMe) throws Exception {
        // 如果选择记住我,则token的过期时间为
        long expiration = isRememberMe ? EXPIRATION_REMEMBER : EXPIRATION;

        HashMap<String, Object> map = new HashMap<>();

        map.put(ROLE_CLAIMS, details.getAuthorities()); // 角色名字
        return Jwts.builder().signWith(SignatureAlgorithm.HS512, SECRET) // 加密算法
                .setClaims(map) // 自定义信息
                .setIssuer(ISS) // jwt发行人
                .setSubject(details.getUsername()) // jwt面向的用户
                .setIssuedAt(new Date()) // jwt发行人
                .setExpiration(new Date(System.currentTimeMillis() + expiration)) // key过期时间
                .compact();
    }

    /**
     * 从token获取用户信息
     *
     * @param token
     * @return
     */
    public static String getUsername(String token) throws Exception {
        return getTokenBody(token).getSubject();
    }

    /**
     * 从token中获取用户角色
     *
     * @param token
     * @return
     */
    @SuppressWarnings("unchecked")
    public static Set<String> getUserRole(String token) throws Exception {
        List<GrantedAuthority> userAuthorities = (List<GrantedAuthority>) getTokenBody(token).get(ROLE_CLAIMS);
        return AuthorityUtils.authorityListToSet(userAuthorities);
    }

    /**
     * 是否已过期
     *
     * @param token
     * @return
     */
    public static boolean isExpiration(String token) throws Exception {
        return getTokenBody(token).getExpiration().before(new Date());
    }

    private static Claims getTokenBody(String token) throws Exception {
        return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
    }

    /**
     * 验证token
     *
     * @param token
     * @param userDetails
     * @return
     */
    public static boolean validateToken(String token, UserDetails userDetails) throws Exception {
        User user = (User) userDetails;
        final String username = getUsername(token);
        return (username.equals(user.getUsername()) && isExpiration(token) == false);
    }

}

Result 响应信息类,这个类在做普通的demo可有可无,也就是定义了一些响应给客户端的样式。

package com.dly.utils;

import java.io.Serializable;
import java.util.Date;

public class Result<T> implements Serializable {
    private Integer code;
    private String message;
    private Long date;
    private T data;


    public Result(StatusCode statusCode, T data) {
        this.code = statusCode.getCode();
        this.message = statusCode.getMessage();
        this.date = new Date().getTime();
        this.data = data;
    }

    public Result(StatusCode statusCode) {
        this(statusCode, null);

    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Long getDate() {
        return date;
    }

    public void setDate(Long date) {
        this.date = date;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public static Result success() {
        return new Result(StatusCode.SUCCESS);
    }


    public static Result success(Object data) {
        return new Result(StatusCode.SUCCESS, data);
    }

    public static Result fail(StatusCode statusCode) {
        return new Result(statusCode);
    }

    public static Result fail(Object data) {
        return new Result(StatusCode.SUCCESS, data);
    }
}

到此,两个工具类已经定义好了。接下来就是重头戏。

UserDetailServiceImpl、自定义登录逻辑,

package com.dly.service.impls;

import com.dly.bean.Admin;
import com.dly.bean.Teacher;
import com.dly.dao.AdminDao;
import com.dly.dao.TeacherDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;


import java.util.Objects;
@Component("userDetailServiceImpl")   // 因为是自定义登录逻辑,是UserDetailsService的实现类,而他的实现类不止一个,所以要给他一个独有的id
public class UserDetailServiceImpl implements UserDetailsService {
	//  用于代码加密,这时候对象还没有创建、 在后面的securityConfig配置类里面,我会将他加入到spring容器里面。
    @Autowired
    private PasswordEncoder passwordEncoder;
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        if (Objects.equals(userName, "admin")) {
            String password = passwordEncoder.encode(123);
            return new User(userName, password, AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_admin"));
        }
    }
}

说明:当我们通过通过引入了springSecurity时,进行认证的时候,就会调用这个实现类的loadUserByUserName方法,通过名字,便可以看出来,就是通过username加载用户,实际上就是如果认证成功,就会创建一个username为admin,角色为(ROLE_admin为固定写法,除此之外还有权限,,可以通过上面的官方文档进行了解)admin的认证对象。

本来接下来我应该编写Security的配置类了的,但是这样可能不太好让读者理解,接下来我就编写,登录(认证)成功,(认证)登录失败的处理器。
LoginSuccessHandler(登陆成功处理器):如果认证成功之后就会跳入这个类的onAuthenticationSuccess方法。

package com.dly.config;


import com.dly.bean.proj.Message;
import com.dly.utils.JwtTokenUtils;
import com.dly.utils.Result;
import com.dly.utils.StatusCode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class LoginSuccessHandler implements AuthenticationSuccessHandler {
    @Autowired
    @Qualifier("userDetailServiceImpl")  // 这里获取到的UserDetailsService对象要我们之前自定义的
    private UserDetailsService userDetailsService;

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        try {
            User details = (User) userDetailsService.loadUserByUsername(authentication.getName());
            // 通过我们之前编写的jwt工具类创建token
             // 这里是加上一个前缀,其实可加可不加,如果加了,在后续的拦截的时候,记得把前缀给剥离掉,如果不加,就不用剥离
            String token = JwtTokenUtils.TOKEN_PREFIX  + JwtTokenUtils.createToken(details, false); 
            // 将token设置到相应头里面去
            httpServletResponse.setHeader(JwtTokenUtils.TOKEN_HEADER, token);
            // Message是我包装的一个类,为的是让前端小姐姐方便,读者可以不必这样将token放到message里面,可以直接write(token出去)。
            Message message = new Message();
            message.setTOKEN(token);
            message.setData(null);
            httpServletResponse.getWriter().write(objectMapper.writeValueAsString(Result.success(message)));
        } catch (Exception e) {
            httpServletResponse.setContentType("application/json;charset=UTF-8");
           // StatusCode 也是类似的一个包装类,读者可以直接抛出其他异常。
            httpServletResponse.getWriter().write(objectMapper.writeValueAsString(StatusCode.USER_LOGIN_ERROR));
        }

    }
}

**到此:**认证成功处理器已经建好了,但是还不能用,此刻的他只是一个普通的类,即使登录成功也不会起作用,还需要在后文的securityConfig进行引入配置,才能起作用。,认证失败处理器也是如此。

LoginFailHandler(登陆失败处理器):登录失败处理器,其实就是你认证失败了要做什么。我们做的事情很少,就是告诉客户端,老子登录认证失败了。也就是说登录失败就失败了,可有可无,但是在实际前后端开发中,你总得那啥一下吧,不能这么随便。

package com.dly.config;


import com.dly.utils.StatusCode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class LoginFailHandler implements AuthenticationFailureHandler {


    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception) throws IOException, ServletException {

        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString("老夫登录失败了”));
    }
}

接下来配置全局拦截器,最后我们再来配置SecurityConfig配置类。

JwtAuthenticationTokenFilter(这是springboot的一个原生过滤器,通过父类可以看出来,只进行一次过滤。网上还是有许多文章描写这个东西的,这里不细说,因为我还没有了解透彻,哈哈): 全局拦截器,任何客户端发过来的请求请求都要经过此过滤器,而我们就在这里进行,token的比对校验,校验token是否正确,校验token是否过期等。

package com.dly.handler;
import com.dly.utils.JwtTokenUtils;
import com.dly.utils.StatusCode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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.WebAuthenticationDetails;
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 {

    @Autowired
    @Qualifier("userDetailServiceImpl")
    private UserDetailsService userDetailService;

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
		// 其实在这里少了一步,就是在前后端分离架构时,vue发过来的请求会有一个预请求,请求方式是"OPTION",而我们要在这里请求一下,放行他。
		// 从请求头获取token “TOKEN” 是使用spring4all下的swagger中默认的名字,开发中要和请求头的key保持一致。
        String token = httpServletRequest.getHeader("TOKEN");
        //注意: 如果在登录成功处理器那里没有进行token前缀的添加,这里也不需要剥离。
        if (token != null && StringUtils.startsWith(token, JwtTokenUtils.TOKEN_PREFIX)) {
            token = StringUtils.substring(token, JwtTokenUtils.TOKEN_PREFIX.length());
        } else {
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }
        try {
            String username = JwtTokenUtils.getUsername(token);
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                /*
                 * 	注意:
                 * 		 这里代码不应该从数据库中去查,而是从缓存中根据token去查,目前只是做测试,无关紧要
                 * 		如果是真正的项目实际开发需要增加缓存
                 */
                UserDetails userDetails = userDetailService.loadUserByUsername(username);

                if (JwtTokenUtils.validateToken(token, userDetails)) {
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                            userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetails(httpServletRequest));
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }

            }
        } catch (Exception e) {
            httpServletResponse.setContentType("application/json;charset=UTF-8");
            httpServletResponse.getWriter().write(objectMapper.writeValueAsString(StatusCode.SYSTEM_INNER_ERROR));
            return;
        }
        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }

}

其他说明:关于SecurtityContextHolder是spring securty的一个工具类,相关介绍,前面提供的官方文档是有的。
在这里插入图片描述
在这里插入图片描述

最后一个就是前文多次提到的securityconfig,这个东西很重要,但是却最容易理解。
SecurityConfig: springsecurity 相关的配置,在这里要做的事情挺多的:自定义登录表单,引入前面自定义登录成功,自定义登录失败,以及全局拦截的配置,如果前后端分离,这里还要配置允许跨域(官方文档和网上都有介绍),以及权限(角色)的接口放行,以及swagger资源的放行,等等。很多

package com.dly.config;
import com.dly.handler.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsConfigurationSource;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;


@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	// 自定义登录逻辑的对象的引入
    @Autowired
    @Qualifier("userDetailServiceImpl")
    private UserDetailsService userDetailsService;

// 前文配置的全局拦截器
    @Bean
    public JwtAuthenticationTokenFilter getauthenticationTokenFilterBean() {
        return new JwtAuthenticationTokenFilter();
    }

// 登录成功处理器
    @Bean
    public LoginSuccessHandler getLoginSuccessHandler() {
        return new LoginSuccessHandler();
    }
    // 登录失败处理器
	@Bean
    public LoginFailHandler getLoginFailHandler() {
        return new LoginFailHandler();
    }
    // spring security 自带的密码加密,在前文自定义登录逻辑里面使用到
    @Bean
    public PasswordEncoder getPw() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
        		// 自定义登录表单 因为是前后端分离,等一下controller要模拟登录表单,前端vue也要模拟成表单,不能是ajax。
                .loginPage("/authentications/login")
                // 必须是post请求
                .loginProcessingUrl("/user/login")
                // 将前文的成功处理器配置进去
                .successHandler(getLoginSuccessHandler())
                // 将前文的登录失败处理器配置进去
                .failureHandler(getLoginFailHandler())
                .and()
                // 关掉跨域请求伪造
                .csrf().disable() //使用jwt,不需要csrf
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) //基于token,不需要session
                .and()
                .authorizeRequests()
                // 设置允许访问的资源, 读者可以自定义
                .antMatchers("/authentications/login").permitAll()
               // 登录post肯定是要放行的呀
                .antMatchers("/user/login").permitAll()
                // 设置允许访问的资源
                .antMatchers("/webjars/**").permitAll()
                // swagger资源要放行。
                .antMatchers(
                        "/v2/api-docs",
                        "/swagger-resources",
                        "/swagger-resources/**",
                        "/configuration/ui",
                        "/configuration/security",
                        "/swagger-ui.html/**",
                        "/webjars/**"

                ).permitAll()
                .antMatchers("/user/**", "/test/**").hasAnyRole("admin", "teacher", "student")
                .antMatchers("/teacher/**").hasAnyRole("teacher", "admin")
                .antMatchers("/college/**",
                        "/professionClass/**",
                        "/problem/**",
                        "/course/**").hasRole("admin")
                .antMatchers("/student/**").hasAnyRole("admin","student")
                // 任何请求都要认证。
                .anyRequest().authenticated();
        http.cors(Customizer.withDefaults());

        // 禁用缓存
        http.headers().cacheControl();
        // 添加JWT filter
        http.addFilterBefore(getauthenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
    }
}

说明: 有关springsecurity 的主要配置在上面我已经用注释写好了,上面代码是我从我的某个项目复制下来的,做了一些删除和修改。,如果读者直接使用,我不保证一定可以用,如果不能用就去官方文档上看看。

此时基本架构已经完成,接下来下面就是对整个流程进行梳理

五、流程梳理。

5.1、流程图。

说明:以下流程图只是大致的,spring securty有着一条很长很长的责任链,及其复杂,如果想要去了解,这有位博主的博客写得很棒,去看一下。卧槽:我找不到那篇博客了,竟然没有收藏,,,,,,找到了之后我再去评论里面放链接

在这里插入图片描述

接下来,我们开始最后一步:自定义登录处理器(也就是前文的securtyConfig里面第一个配置项)
在这里插入图片描述

六、自定义登录表单(前后端分离,咋们模拟表单)

package com.dly.controller;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.dly.utils.Result;
import com.dly.utils.StatusCode;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;



/**
 * @program: paz
 * @description: 发送请求,如果token为空,跳转到这个controller
 * @author: wangzh
 * @create: 2019-03-21 15:41
 */
@RestController
@RequestMapping("/authentications")
public class SecurityController {

    private RequestCache requestCache = new HttpSessionRequestCache();

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    /**
     * 当需要身份认证时,跳转到这里
     *
     * @param request
     * @param response
     * @return
     * @throws IOException
     */
    @GetMapping("/login")
    @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
    public Result<String> requireAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws IOException {

        SavedRequest savedRequest = requestCache.getRequest(request, response);

        if (savedRequest != null) {
            String targetUrl = savedRequest.getRedirectUrl();
            if (StringUtils.endsWithIgnoreCase(targetUrl, ".html")) {
                // TODO 跳转到登陆页面
                redirectStrategy.sendRedirect(request, response, "/login.html");
            }
        }
        return Result.success(StatusCode.PERMISSION_NO_ACCESS);
    }
    @GetMapping("/logout")
    public Result logoutSuccess() {
        return  Result.success();
    }


}

说明上面注释已经写得很清楚,loginForm为get
loginUrl为post:接下来写的是post请求的

package com.dly.controller;
import com.dly.bean.proj.Message;
import com.dly.utils.JwtTokenUtils;
import com.dly.utils.Result;
import com.dly.utils.StatusCode;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;

@Api(tags = "用户登录")
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    @Qualifier("userDetailServiceImpl")
    private UserDetailsService userDetailsService;

    @CrossOrigin
    @RequestMapping(value = "/login",method = RequestMethod.POST)
    @ApiOperation("登录")
    public void Login(String username,String password) {
        // 这里必须是post请求
        // 在这里写一个空实现,剩下的就给userdetail实现就行了
    }

    @GetMapping("/getUserDetailByToken")
    @ApiOperation(value = "根据token得到用户信息")
    public Result<UserDetails> getUserDetailByToken(HttpServletRequest request, HttpServletResponse response) throws Exception {
        String token = request.getHeader("TOKEN");
        response.setContentType("application/json;charset=UTF-8");
        if (token != null && StringUtils.startsWith(token, JwtTokenUtils.TOKEN_PREFIX)) {
            token = StringUtils.substring(token, JwtTokenUtils.TOKEN_PREFIX.length());
            UserDetails details = userDetailsService.loadUserByUsername(JwtTokenUtils.getUsername(token));
            Message message = new Message();
            Collection<? extends GrantedAuthority> authorities = details.getAuthorities();
            String[] roles = new String[authorities.size()];
            int index = 0;
            for(GrantedAuthority grantedAuthority:authorities) {
               roles[index] = grantedAuthority.getAuthority();
               index++;
            }
            message.setRoles(roles);
            message.setUsername(details.getUsername());
            message.setPassword(details.getPassword());
            return Result.success(message);
        } else {
            return Result.fail(StatusCode.USER_ACCOUNT_FORBIDDEN);
        }
    }
 
}

对应的就是上面的(看下图哦)

在这里插入图片描述
至此:jjwt+springboot+spring securty已经整合结束了。

七、简单的测试、和其他总结。

7.1、测试。

在这里插入图片描述
认证成功,响应回来token
在这里插入图片描述
将token放到请求头:
在这里插入图片描述
这样就认证成功了。接下来就可以访问admin拥有的权限可以访问的接口了,本文主要以整合springboot + spring securty + jjwt实现前后端分离为主,具体的权限测试我就不测了。

7.2、有关于其他注意事项(进行和vue整合)。

  • 最后如何你还需要和vue进行整合,还要解决跨域问题,因为整合了spring securty,传统的springboot解决跨域问题已经不够了,还需要去securtyConfig里面进行springsecurty的跨域处理。
  • 因为我们登陆是模拟表单登陆的,所以在vue那边发过来的请求,一般是json为主,所以需要将其转成表单形式,然后才能进行登陆,具体怎么转,百度吧,很简单的。
  • 还有什么其他的暂时没有想到。—,对了感谢某位秃头猿猿的技术分享。哈哈

学习永无止境,相信爱情相信光。

热门文章

暂无图片
编程学习 ·

Java输出数组的内容

Java输出数组的内容_一万个小时-CSDN博客_java打印数组内容1. 输出内容最常见的方式// List<String>类型的列表List<String> list new ArrayList<String>();list.add("First");list.add("Second");list.add("Third");list.ad…
暂无图片
编程学习 ·

母螳螂的“魅惑之术”

在它们对大蝗虫发起进攻的时候&#xff0c;我认认真真地观察了一次&#xff0c;因为它们突然像触电一样浑身痉挛起来&#xff0c;警觉地面对限前这个大家伙&#xff0c;然后放下自己优雅的身段和祈祷的双手&#xff0c;摆出了一个可怕的姿势。我被眼前的一幕吓到了&#xff0c;…
暂无图片
编程学习 ·

疯狂填词 mad_libs 第9章9.9.2

#win7 python3.7.0 import os,reos.chdir(d:\documents\program_language) file1open(.\疯狂填词_d9z9d2_r.txt) file2open(.\疯狂填词_d9z9d2_w.txt,w) words[ADJECTIVE,NOUN,VERB,NOUN] str1file1.read()#方法1 for word in words :word_replaceinput(fEnter a {word} :)str1…
暂无图片
编程学习 ·

HBASE 高可用

为了保证HBASE是高可用的,所依赖的HDFS和zookeeper也要是高可用的. 通过参数hbase.rootdir指定了连接到Hadoop的地址,mycluster表示为Hadoop的集群. HBASE本身的高可用很简单,只要在一个健康的集群其他节点通过命令 hbase-daemon.sh start master启动一个Hmaster进程,这个Hmast…
暂无图片
编程学习 ·

js事件操作语法

一、事件的绑定语法 语法形式1 事件监听 标签对象.addEventListener(click,function(){}); 语法形式2 on语法绑定 标签对象.onclick function(){} on语法是通过 等于赋值绑定的事件处理函数 , 等于赋值本质上执行的是覆盖赋值,后赋值的数据会覆盖之前存储的数据,也就是on…
暂无图片
编程学习 ·

Photoshop插件--晕影动态--选区--脚本开发--PS插件

文章目录1.插件界面2.关键代码2.1 选区2.2 动态晕影3.作者寄语PS是一款栅格图像编辑软件&#xff0c;具有许多强大的功能&#xff0c;本文演示如何通过脚本实现晕影动态和选区相关功能&#xff0c;展示从互联网收集而来的一个小插件&#xff0c;供大家学习交流&#xff0c;请勿…
暂无图片
编程学习 ·

vs LNK1104 无法打开文件“xxx.obj”

写在前面&#xff1a; 向大家推荐两本新书&#xff0c;《深度学习计算机视觉实战》和《学习OpenCV4&#xff1a;基于Python的算法实战》。 《深度学习计算机视觉实战》讲了计算机视觉理论基础&#xff0c;讲了案例项目&#xff0c;讲了模型部署&#xff0c;这些项目学会之后可以…
暂无图片
编程学习 ·

工业元宇宙的定义与实施路线图

工业元宇宙的定义与实施路线图 李正海 1 工业元宇宙 给大家做一个关于工业元宇宙的定义。对于工业&#xff0c;从设计的角度来讲&#xff0c;现在的设计人员已经做到了普遍的三维设计&#xff0c;但是进入元宇宙时代&#xff0c;就不仅仅只是三维设计了&#xff0c;我们的目…
暂无图片
编程学习 ·

【leectode 2022.1.15】完成一半题目

有 N 位扣友参加了微软与力扣举办了「以扣会友」线下活动。主办方提供了 2*N 道题目&#xff0c;整型数组 questions 中每个数字对应了每道题目所涉及的知识点类型。 若每位扣友选择不同的一题&#xff0c;请返回被选的 N 道题目至少包含多少种知识点类型。 示例 1&#xff1a…
暂无图片
编程学习 ·

js 面试题总结

一、js原型与原型链 1. prototype 每个函数都有一个prototype属性&#xff0c;被称为显示原型 2._ _proto_ _ 每个实例对象都会有_ _proto_ _属性,其被称为隐式原型 每一个实例对象的隐式原型_ _proto_ _属性指向自身构造函数的显式原型prototype 3. constructor 每个prot…
暂无图片
编程学习 ·

java练习代码

打印自定义行数的空心菱形练习代码如下 import java.util.Scanner; public class daYinLengXing{public static void main(String[] args) {System.out.println("请输入行数");Scanner myScanner new Scanner(System.in);int g myScanner.nextInt();int num g%2;//…
暂无图片
编程学习 ·

RocketMQ-什么是死信队列?怎么解决

目录 什么是死信队列 死信队列的特征 死信消息的处理 什么是死信队列 当一条消息初次消费失败&#xff0c;消息队列会自动进行消费重试&#xff1b;达到最大重试次数后&#xff0c;若消费依然失败&#xff0c;则表明消费者在正常情况下无法正确地消费该消息&#xff0c;此时…
暂无图片
编程学习 ·

项目 cg day04

第4章 lua、Canal实现广告缓存 学习目标 Lua介绍 Lua语法 输出、变量定义、数据类型、流程控制(if..)、循环操作、函数、表(数组)、模块OpenResty介绍(理解配置) 封装了Nginx&#xff0c;并且提供了Lua扩展&#xff0c;大大提升了Nginx对并发处理的能&#xff0c;10K-1000K Lu…
暂无图片
编程学习 ·

输出三角形

#include <stdio.h> int main() { int i,j; for(i0;i<5;i) { for(j0;j<i;j) { printf("*"); } printf("\n"); } }
暂无图片
编程学习 ·

stm32的BOOTLOADER学习1

序言 最近计划学习stm32的BOOTLOADER学习,把学习过程记录下来 因为现在网上STM32C8T6还是比较贵的,根据我的需求flash空间小一些也可以,所以我决定使用stm32c6t6.这个芯片的空间是32kb的。 #熟悉芯片内部的空间地址 1、flash ROM&#xff1a; 大小32KB&#xff0c;范围&#xf…
暂无图片
编程学习 ·

通过awk和shell来限制IP多次访问之学不会你打死我

学不会你打死我 今天我们用shell脚本&#xff0c;awk工具来分析日志来判断是否存在扫描器来进行破解网站密码——限制访问次数过多的IP地址&#xff0c;通过Iptables来进行限制。代码在末尾 首先我们要先查看日志的格式&#xff0c;分析出我们需要筛选的内容&#xff0c;日志…
暂无图片
编程学习 ·

Python - 如何像程序员一样思考

在为计算机编写程序之前&#xff0c;您必须学会如何像程序员一样思考。学习像程序员一样思考对任何学生都很有价值。以下步骤可帮助任何人学习编码并了解计算机科学的价值——即使他们不打算成为计算机科学家。 顾名思义&#xff0c;Python经常被想要学习编程的人用作第一语言…
暂无图片
编程学习 ·

蓝桥杯python-数字三角形

问题描述 虽然我前后用了三种做法&#xff0c;但是我发现只有“优化思路_1”可以通过蓝桥杯官网中的测评&#xff0c;但是如果用c/c的话&#xff0c;每个都通得过&#xff0c;足以可见python的效率之低&#xff08;但耐不住人家好用啊&#xff08;哭笑&#xff09;&#xff09…