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为主,所以需要将其转成表单形式,然后才能进行登陆,具体怎么转,百度吧,很简单的。
- 还有什么其他的暂时没有想到。—,对了感谢某位秃头猿猿的技术分享。哈哈