mybatisplus+SpringBoot项目整合SpringSecurity及JWT实现登录认证

此项目为前后端分离项目mybatisplus+SpringBoot项目整合SpringSecurity及JWT实现登录认证
在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 token的工具类


package com.ff.jwt;

import com.ff.result.ResultCode;
import com.ff.result.ResultObj;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
 * JwtToken生成的工具类
 *
 */
public class JWTUtil {
    
    

    public static String createToken(UserDetails userDetails){
    
    
        // token由三部分组成:头部、有效负载,签名
        // 1.头部信息
        Map<String,Object> headerMap = new HashMap<>();
        headerMap.put("type","JWT");
        headerMap.put("alg","HS256");

        // 2.有效负载
        Map<String,Object> payload = new HashMap<>();
        payload.put("username",userDetails.getUsername());
        payload.put("date",new Date());

        // 有效时间
        long timeMillis = System.currentTimeMillis();
        long endTime = timeMillis+6000000;

        // 3.签名
        String token = Jwts.builder().setHeader(headerMap)
                .setClaims(payload)
                .setExpiration(new Date(endTime))
                .signWith(SignatureAlgorithm.HS256, "jq1223")
                .compact();
        return token;
    }

	 
    /**
     * 从token中获取JWT中的负载
     */
    public static ResultObj verifyToken(String token) {
    
    
        try {
    
    
            Claims claims = Jwts.parser().setSigningKey("jq1223")
                    .parseClaimsJws(token)
                    .getBody();
            return ResultObj.success(claims);
        } catch (Exception e) {
    
    
            return ResultObj.error(ResultCode.TOKEN_ERROR);
        }
    }
	  
    /**
     * 从token中获取登录用户名
     */
    public static String getUsername(String token){
    
    
        try {
    
    
            Claims claims = Jwts.parser().setSigningKey("jq1223")
                    .parseClaimsJws(token)
                    .getBody();
            return (String) claims.get("username");
        } catch (Exception e) {
    
    
            return null;
        }
    }
}


添加SpringSecurity的配置类
package com.fh.config.security;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fh.config.security.bo.AdminUserDetails;
import com.fh.config.security.filter.JwtAuthenticationTokenFilter;
import com.fh.config.security.filter.RestAuthenticationEntryPoint;
import com.fh.config.security.filter.RestfulAccessDeniedHandler;

import com.fh.resource.model.Resource;
import com.fh.role.model.Role;
import com.fh.user.model.Admin;
import com.fh.user.service.AdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.Filter;
import java.util.List;

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    

    @Autowired
    private AdminService adminService;

    @Autowired
    private RestAuthenticationEntryPoint restAuthenticationEntryPoint;

    @Autowired
    private RestfulAccessDeniedHandler restfulAccessDeniedHandler;


    /**
     * 类似于拦截器,配置哪些方法需要拦截,哪些方法不需要拦截
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
              http.csrf()// 由于使用的是JWT,我们这里不需要csrf
        		.disable()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)// 基于token,所以不需要session
                .and()
                .authorizeRequests() // 哪些请求需要放开
                .antMatchers("/login/**","/upload/**")// 对登录注册要允许匿名访问
                .permitAll()
                .antMatchers(HttpMethod.OPTIONS)
                .permitAll()
                .anyRequest() // 其他请求都需要拦截
                .authenticated();
        // 禁用缓存
        http.headers().cacheControl();
        //添加 JWT Filter
        http.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
       //添加自定义未知授权和登录结果返回
        http.exceptionHandling()
                .accessDeniedHandler(restfulAccessDeniedHandler)
                .authenticationEntryPoint(restAuthenticationEntryPoint);
    }

    @Bean
    protected JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {
    
    
        return new JwtAuthenticationTokenFilter();
    }

    /**
     * 登录和认证
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        auth.userDetailsService(userDetailsService())
                .passwordEncoder(passwordEncoder());
    }

    @Bean
    protected PasswordEncoder passwordEncoder(){
    
    
        return new BCryptPasswordEncoder();
    }

    /**
     * 当前用户所拥有的角色和权限信息
     * @return
     */
    @Bean
    @Override
    protected UserDetailsService userDetailsService() {
    
    
        return username ->{
    
    
            QueryWrapper<Admin> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("username",username);
            Admin admin = adminService.getOne(queryWrapper);
            if (admin != null){
    
    
               List<Resource> resourceList = adminService.queryResourceList(admin.getId());
               List<Role> roleList = adminService.queryRoleList(admin.getId());
               return new AdminUserDetails(admin,resourceList,roleList);
            }
            throw new UsernameNotFoundException("用户不存在");
        };
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
    
    
        return super.authenticationManagerBean();
    }
}

相关依赖及方法说明
configure(HttpSecurity httpSecurity):用于配置需要拦截的url路径、jwt过滤器及出异常后的处理器;
configure(AuthenticationManagerBuilder auth):用于配置UserDetailsService及PasswordEncoder;
RestfulAccessDeniedHandler:当用户没有访问权限时的处理器,用于返回JSON格式的处理结果;
RestAuthenticationEntryPoint:当未登录或token失效时,返回JSON格式的结果;
UserDetailsService:SpringSecurity定义的核心接口,用于根据用户名获取用户信息,需要自行实现;
UserDetails:SpringSecurity定义用于封装用户信息的类(主要是用户信息和权限),需要自行实现;
PasswordEncoder:SpringSecurity定义的用于对密码进行编码及比对的接口,目前使用的是BCryptPasswordEncoder;
JwtAuthenticationTokenFilter:在用户名和密码校验前添加的过滤器,如果有jwt的token,会自行根据token信息进行登录。

添加RestfulAccessDeniedHandler

package com.ff.config.security.filter;

import cn.hutool.json.JSONUtil;
import com.ff.result.ResultCode;
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 httpServletRequest, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
    
    
        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json");
        response.getWriter().println(JSONUtil.parse(ResultCode.NO_PERMISSION));
        response.getWriter().flush();
    }
}

添加RestAuthenticationEntryPoint

package com.ff.config.security.filter;

import cn.hutool.json.JSONUtil;
import com.ff.result.ResultCode;
import com.ff.result.ResultObj;
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 httpServletRequest, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
    
    
        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json");
        response.getWriter().println(JSONUtil.parse(ResultCode.TOKEN_ERROR));
        response.getWriter().flush();
    }
}

添加AdminUserDetails

package com.ff.config.security.bo;


import com.ff.resource.model.Resource;
import com.ff.role.model.Role;
import com.ff.user.model.Admin;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * SpringSecurity需要的用户详情
 */
public class AdminUserDetails implements UserDetails {
    
    

    private Admin admin;
    private List<Resource> resourceList;
    private List<Role> roleList;

    public AdminUserDetails(Admin admin, List<Resource> resourceList, List<Role> roleList) {
    
    
        this.admin = admin;
        this.resourceList = resourceList;
        this.roleList = roleList;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
    
    
        List<SimpleGrantedAuthority> list = new ArrayList<>();
        resourceList.forEach(resource -> {
    
    
            list.add(new SimpleGrantedAuthority(resource.getKeyword()));
        });

        roleList.forEach(role -> {
    
    
            list.add(new SimpleGrantedAuthority(role.getKeyword()));
        });
        return list;
    }

    @Override
    public String getPassword() {
    
    
        return admin.getPassword();
    }

    @Override
    public String getUsername() {
    
    
        return admin.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
    
    
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
    
    
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
    
    
        return true;
    }

    @Override
    public boolean isEnabled() {
    
    
        return admin.getStatus().equals(1);
    }
}

添加JwtAuthenticationTokenFilter

在用户名和密码校验前添加的过滤器,如果请求中有jwt的token且有效,会取出token中的用户名,然后调用SpringSecurity的API进行登录操作。
package com.ff.config.security.filter;

import com.ff.jwt.JWTUtil;
import com.ff.result.ResultObj;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
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.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;
import java.util.concurrent.TimeUnit;
/**
 * JWT登录授权过滤器
 * 
 */
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    
    

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private RedisTemplate redisTemplate;
    //token key
    private static final String pre_token="ACCESS_TOKEN:";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    
    

        // 获取token
        String token = request.getHeader("Authorization-token");
        if (StringUtils.isNotBlank(token)){
    
    
            ResultObj resultObj = JWTUtil.verifyToken(token);
            if (resultObj.getCode() == 200){
    
    
                String username=JWTUtil.getUsername(token);
                UserDetails userDetails=userDetailsService.loadUserByUsername(username);
                String accessKey=pre_token+userDetails.getUsername()+":"+token;
                if(redisTemplate.hasKey(accessKey)){
    
    
                    UsernamePasswordAuthenticationToken authenticationToken=new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
                    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                    Long currentTime=System.currentTimeMillis();
                    //续签:
                    redisTemplate.opsForValue().set(accessKey,currentTime);
                    redisTemplate.expire(accessKey,2, TimeUnit.HOURS);
                }
            }
        }
        filterChain.doFilter(request,response);
    }
}
添加LoginController 类

摘自项目源码地址
添加LoginController 类

package com.ff.controller;

import com.ff.result.ResultCode;
import com.ff.result.ResultObj;
import com.ff.user.model.Admin;
import com.ff.user.service.AdminService;
import org.apache.commons.lang3.StringUtils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;



@RestController
@RequestMapping("login")
@CrossOrigin
public class LoginController {
    
    

    @Autowired
    private AdminService adminService;

    @PostMapping("logins")
    public ResultObj login(String username,String password){
    
    
        if (StringUtils.isBlank(username) || StringUtils.isBlank(password)){
    
    
            return ResultObj.error(ResultCode.USER_PWD_ISNULL);
        }
        //判断用户名是否存在
        Admin admin = adminService.queryUserByUsername(username);
        if (admin == null) {
    
    
            return ResultObj.error(ResultCode.USER_NOEXIST);
        }



        String token = adminService.login(username,password);

        if (token == null){
    
    
            ResultObj.error(ResultCode.TOKEN_ERROR);
        }

        return ResultObj.success(token);
    }
}

添加AdminServiceImpl 类

package com.ff.user.service.impl;



import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.ff.jwt.JWTUtil;
import com.ff.resource.model.Resource;
import com.ff.result.ResultCode;
import com.ff.result.ResultObj;
import com.ff.role.model.Role;
import com.ff.user.mapper.AdminMapper;
import com.ff.user.model.Admin;
import com.ff.user.service.AdminService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.BadCredentialsException;
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.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 后台用户表 服务实现类
 * </p>
 *
 * @author @jq
 * @since 2021-03-09
 */
@Service
public class AdminServiceImpl extends ServiceImpl<AdminMapper, Admin> implements AdminService {
    
    

    @javax.annotation.Resource
    private AdminMapper adminMapper;

    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private PasswordEncoder passwordEncoder;

    private static final String pre_token = "ACCESS_TOKEN:";

    @Autowired
    private RedisTemplate redisTemplate;


    @Override
    public List<Resource> queryResourceList(Long id) {
    
    
        return adminMapper.queryResourceList(id);
    }

    @Override
    public List<Role> queryRoleList(Long id) {
    
    
        return adminMapper.queryRoleList(id);
    }

    @Override
    public String login(String username, String password) {
    
    
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);

        if (!passwordEncoder.matches(password,userDetails.getPassword())){
    
    
            throw new BadCredentialsException("密码错误");
        }
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        String token = JWTUtil.createToken(userDetails);


        long timeMillis = System.currentTimeMillis();
        String accessKey = pre_token+userDetails.getUsername()+":"+token;
        redisTemplate.opsForValue().set(accessKey,timeMillis);
        redisTemplate.expire(accessKey,2,TimeUnit.MINUTES);
        return token;

    }

    判断用户名是否存在
    @Override
    public Admin queryUserByUsername(String username) {
    
    
        return adminMapper.selectOne(new QueryWrapper<Admin>().eq("username",username));
    }
}

添加AdminMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ff.user.mapper.AdminMapper">

    <select id="queryResourceList" parameterType="java.lang.Long" resultType="com.ff.resource.model.Resource">
        SELECT DISTINCT
            r.id,
            r.keyword
        FROM
            ums_admin_role_relation ar
                LEFT JOIN ums_role_resource_relation rr ON ar.role_id = rr.role_id
                LEFT JOIN ums_resource r ON rr.resource_id = r.id
        WHERE
            ar.admin_id = #{
    
    id}
          AND r.keyword IS NOT NULL
    </select>

    <select id="queryRoleList" parameterType="java.lang.Long" resultType="com.fh.role.model.Role">
        SELECT
            r.id,
            r.keyword
        FROM
            ums_admin_role_relation ar
                LEFT JOIN ums_role r ON ar.role_id = r.id
        WHERE
            ar.admin_id = #{
    
    id}
    </select>
</mapper>

可能会报错的地方:
在这里插入图片描述
解决方案:查看需要查询的接口中有没有需要判断不能为空的条件在这里插入图片描述
前端request.js

import axios from 'axios';

const service = axios.create({
    
    
    timeout: 500000000000
});

service.interceptors.request.use(config=>{
    
    
    // alert(localStorage.getItem("token"));
    config.headers.common["Authorization-token"]=localStorage.getItem("token");
    return config;
})

service.interceptors.response.use(response=>{
    
    
    var code=response.data.code;
    if( code != 200){
    
    
        router.push("/login");
        return Promise.reject('error');
    }
    return response;
})
export default service;

猜你喜欢

转载自blog.csdn.net/jq1223/article/details/115263322