欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

spring boot+spring security+jwt+vue整合前后端分离token权限认证项目详细过程代码 含AES加密

程序员文章站 2022-06-13 15:17:52
...

准备前提条件:已搭好一个spring boot后台项目及vue前台项目

目录

一、后台

1、依赖

2、继承WebSecurityConfigurerAdapter适配器

3、自定义用户名密码校验 MyAuthenticationProvider

4、登录成功处理逻辑 CustomizeAuthenticationSuccessHandler

5、登录失败处理逻辑 CustomizeAuthenticationFailureHandler

6、匿名用户访问无权限资源时的异常处理 CustomizeAuthenticationEntryPoint

7、会话失效(账号被挤下线)处理逻辑 CustomizeSessionInformationExpiredStrategy

8、登出成功处理逻辑 CustomizeLogoutSuccessHandler

9、UserDetailsService 的实现 UserDetailsServiceImpl

10、访问权限过滤器 AuthorizationFilter

11、token处理工具类 TokenUtil

12、Aes加密工具类 AesEncryptUtil

13、统一消息返回体工具类 JsonResult

14、实体 UsersDomain 字段注解偷懒没加全

15、实体 RoleDomain 字段注解偷懒没加全

二、前台

1、main.js

2、router/index.js

3、store/index.js

4、AES加密解密方法utils/secret.js

5、登录页 login/index


一、后台

1、依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.60</version>
</dependency>
<dependency>
    <groupId>net.minidev</groupId>
    <artifactId>json-smart</artifactId>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

2、继承WebSecurityConfigurerAdapter适配器

import com.example.office.filter.AuthorizationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.sql.DataSource;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private DataSource dataSource;
//    自定义用户名密码校验
    @Autowired
    MyAuthenticationProvider myAuthenticationProvider;
    //登录成功处理逻辑
    @Autowired
    CustomizeAuthenticationSuccessHandler authenticationSuccessHandler;

    //登录失败处理逻辑
    @Autowired
    CustomizeAuthenticationFailureHandler authenticationFailureHandler;

    //匿名用户访问无权限资源时的异常
    @Autowired
    CustomizeAuthenticationEntryPoint authenticationEntryPoint;

    //会话失效(账号被挤下线)处理逻辑
    @Autowired
    CustomizeSessionInformationExpiredStrategy sessionInformationExpiredStrategy;

    //登出成功处理逻辑
    @Autowired
    CustomizeLogoutSuccessHandler logoutSuccessHandler;

    @Bean
    public UserDetailsService userDetailsService() {
        return new UserDetailsServiceImpl();
    }
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Bean
    public PersistentTokenRepository getPersistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        return jdbcTokenRepository;
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(myAuthenticationProvider);
//        auth.userDetailsService(userDetailsService());
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                // 基于token,所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and().authorizeRequests().
                // 所以请求都需要认证
                anyRequest().authenticated().
                // 登出
                and().logout().logoutUrl("/logout").
                permitAll(). // 允许所有用户
                logoutSuccessHandler(logoutSuccessHandler). // 登出成功处理逻辑
                // deleteCookies("JESSIONID"). // 登出之后删除cookie
                // 登入
and().formLogin().loginProcessingUrl("/login").usernameParameter("username").passwordParameter("password").permitAll().
                successHandler(authenticationSuccessHandler).failureHandler(authenticationFailureHandler).
                // 记住我
                and().rememberMe().tokenRepository(getPersistentTokenRepository()).
                tokenValiditySeconds(15*60*1000).
                // 异常处理(权限拒绝、登录失效等)
                and().exceptionHandling().
                authenticationEntryPoint(authenticationEntryPoint). // 匿名用户访问无权限资源时的异常处理
                // 访问过滤器
                and().addFilter(new AuthorizationFilter(authenticationManager())).httpBasic().
                // 会话管理
                and().sessionManagement().
                maximumSessions(1). // 同一账号同时登录最大用户数
                expiredSessionStrategy(sessionInformationExpiredStrategy); // 会话失效(账号被挤下线)处理逻辑
        // 禁用缓存
        http.headers().cacheControl();
    }
}

3、自定义用户名密码校验 MyAuthenticationProvider

import com.example.office.utils.AesEncryptUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;

/**
 * 自定义用户名密码校验
 */
@Component
public class MyAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // 获取用户输入的用户名和密码
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();
        // 登录密码前端加密传输,需先解密
        password = AesEncryptUtil.decrypt(password);
        // 获取封装用户的信息
        UserDetails user = userDetailsService.loadUserByUsername(username);
        if (user == null) {
            throw new BadCredentialsException("查无此用户");
        }
        // 进行密码的对比
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        boolean flag = bCryptPasswordEncoder.matches(password, user.getPassword());
        // 校验通过
        if (flag) {
            // 将权限信息也封装进去
            return new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
        } else {
            throw new BadCredentialsException("用户名或密码错误!");
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return true;
    }
}

4、登录成功处理逻辑 CustomizeAuthenticationSuccessHandler

import com.example.office.utils.JsonResult;
import com.example.office.utils.TokenUtil;
import net.minidev.json.JSONValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
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 CustomizeAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private final static Logger log = LoggerFactory.getLogger(CustomizeAuthenticationSuccessHandler.class);

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        log.info("登录成功!");
        User userDetails = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        String token = TokenUtil.createToken(userDetails);
        JsonResult result = JsonResult.success(userDetails);
        httpServletResponse.setContentType("text/json;charsert=utf-8");
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setHeader("token", token);
        httpServletResponse.getWriter().write(JSONValue.toJSONString(result));
    }
}

5、登录失败处理逻辑 CustomizeAuthenticationFailureHandler

import com.example.office.utils.JsonResult;
import net.minidev.json.JSONValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

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

/**
 * 登录失败请求处理
 */
@Component
public class CustomizeAuthenticationFailureHandler implements AuthenticationFailureHandler {
    private final static Logger log = LoggerFactory.getLogger(CustomizeAuthenticationFailureHandler.class);
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException{

        JsonResult result = null;
        if (e instanceof BadCredentialsException) {
            result = JsonResult.keep("密码错误");
        } else if (e instanceof DisabledException) {
            result = JsonResult.keep("账号不可用");
        } else if (e instanceof InternalAuthenticationServiceException) {
            result = JsonResult.keep("账户不存在");
        } else {
            result = JsonResult.keep("登录失败");
        }
        log.info("登录失败!" + result.getHint());
        httpServletResponse.setContentType("text/json;charset=utf-8");
        httpServletResponse.getWriter().write(JSONValue.toJSONString(result));
    }
}

6、匿名用户访问无权限资源时的异常处理 CustomizeAuthenticationEntryPoint

import com.example.office.utils.JsonResult;
import net.minidev.json.JSONValue;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 未授权的统一处理方式
 */
@Component
public class CustomizeAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException{
        httpServletResponse.setContentType("text/json;charset=utf-8");
        JsonResult result = JsonResult.failure("请先登录!", null);
        httpServletResponse.getWriter().write(JSONValue.toJSONString(result));
    }
}

7、会话失效(账号被挤下线)处理逻辑 CustomizeSessionInformationExpiredStrategy

import com.example.office.utils.JsonResult;
import net.minidev.json.JSONValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.web.session.SessionInformationExpiredEvent;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 会话信息过期策略
 */
@Component
public class CustomizeSessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {
    private final static Logger log = LoggerFactory.getLogger(CustomizeSessionInformationExpiredStrategy.class);
    @Override
    public void onExpiredSessionDetected(SessionInformationExpiredEvent sessionInformationExpiredEvent) throws IOException{
        log.info("账号已在其他地方登录!");
        JsonResult result = JsonResult.failure("账号已在其他地方登陆", null);
        HttpServletResponse httpServletResponse = sessionInformationExpiredEvent.getResponse();
        httpServletResponse.setContentType("text/json;charset=utf-8");
        httpServletResponse.getWriter().write(JSONValue.toJSONString(result));
    }
}

8、登出成功处理逻辑 CustomizeLogoutSuccessHandler

import com.example.office.utils.JsonResult;
import net.minidev.json.JSONValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 登出成功请求处理
 */
@Component
public class CustomizeLogoutSuccessHandler implements LogoutSuccessHandler {
    private final static Logger log = LoggerFactory.getLogger(CustomizeLogoutSuccessHandler.class);
    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException {
        log.info("登出成功!");
        JsonResult result = JsonResult.success();
        httpServletResponse.setContentType("text/json;charset=utf-8");
        httpServletResponse.getWriter().write(JSONValue.toJSONString(result));
    }
}

9、UserDetailsService 的实现 UserDetailsServiceImpl

import com.example.office.domain.RoleDomain;
import com.example.office.domain.UsersDomain;
import com.example.office.service.RoleService;
import com.example.office.service.UsersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    public UsersService usersService;
    @Autowired
    public RoleService roleService;
    @Override
    @Transactional
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        if ((userName == null) || "".equals(userName)) {
            throw new RuntimeException("用户不能为空");
        }
        UsersDomain user = usersService.findByUserName(userName);
        if (user == null) {
            throw new RuntimeException("用户不存在!");
        }
        List<GrantedAuthority> grantedAuthorityList = new ArrayList<>();
        List<RoleDomain> list = roleService.findListByUserId(user.getId());
        list.forEach(item -> {
            GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(item.getRoleName());
            grantedAuthorityList.add(grantedAuthority);
        });
        return new User(user.getUserName(), user.getPassword(), grantedAuthorityList);
    }
}

10、访问权限过滤器 AuthorizationFilter

import com.example.office.utils.JsonResult;
import com.example.office.utils.TokenUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import net.minidev.json.JSONValue;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
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.Date;

/**
 * 访问权限过滤器
 */
public class AuthorizationFilter extends BasicAuthenticationFilter {

    public AuthorizationFilter(AuthenticationManager authManager) {
        super(authManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws IOException, ServletException {
        String tokenHeader = request.getHeader(TokenUtil.TOKEN_HEADER);
        // 如果请求头中没有token信息则直接放行了
        if (!"".equals(tokenHeader)) {
            chain.doFilter(request, response);
            return;
        }
        // 如果请求头中有token,则进行解析,并且设置认证信息
        try {
            SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
        } catch (ExpiredJwtException e) {
            //返回json形式的错误信息
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            JsonResult result = JsonResult.failure(e.getMessage(), e);
            response.getWriter().write(JSONValue.toJSONString(result));
            response.getWriter().flush();
            return;
        }
        super.doFilterInternal(request, response, chain);
    }

    // 这里从token中获取用户信息并新建一个token
    private UsernamePasswordAuthenticationToken getAuthentication(String token) throws ExpiredJwtException {
        Claims claims = TokenUtil.verify(token);
        boolean expiration = claims.getExpiration().before(new Date());
        if (expiration) {
            throw new RuntimeException("过期了");
        } else {
            String username = claims.getSubject();
            UserDetails user = (UserDetails)claims.get("user");
            if (username != null) {
                return new UsernamePasswordAuthenticationToken(username, null,
                        user.getAuthorities()
                );
            }
        }
        return null;
    }

}

11、token处理工具类 TokenUtil

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.userdetails.User;
import java.util.Date;
import java.util.HashMap;

public class TokenUtil {
    public static final String TOKEN_HEADER = "token";
    private static final long EXPIRE_TIME= 15*60*1000;
    private static final String TOKEN_SECRET="tokendemo";  //**盐
    private static final String ISS = "echisan";
    /**
     * 签名生成
     * @param user
     * @return
     */
    public static String createToken(User user){
        String token = null;
        try {
            Date expiresAt = new Date(System.currentTimeMillis() + EXPIRE_TIME);
            HashMap<String, Object> map = new HashMap<>();
            map.put("user", user);
            token = Jwts.builder()
                    .setIssuer(ISS)
                    .setClaims(map)
                    .setSubject(user.getUsername())
                    .setExpiration(expiresAt)
                    .setIssuedAt(new Date())
                    // 使用了HMAC256加密算法。
                    .signWith(SignatureAlgorithm.HS512, TOKEN_SECRET)
                    .compact();;
        } catch (Exception e){
            e.printStackTrace();
        }
        return token;
    }
    /**
     * 获取
     * @param token
     * @return
     */
    public static Claims verify(String token){
        return Jwts.parser().setSigningKey(TOKEN_SECRET).parseClaimsJws(token).getBody();
    }
}

12、Aes加密工具类 AesEncryptUtil

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

/**
 * 加密和解密
 */
public class AesEncryptUtil {
    /**
     * key 和 iv 可以随机生成
     */
    private static String KEY = "aaDJL2d9DfhLZO0z";
    private static String IV = "412ADDSSFA342442";
    /**
     * 加密
     */
    public static String encrypt(String data) {
        return encrypt(data, KEY, IV);
    }
    /**
     * 解密
     */
    public static String decrypt(String data) {
        return decrypt(data, KEY, IV);
    }
    /**
     * 加密方法
     */
    private static String encrypt(String data, String key, String iv) {
        try {
            // "算法/模式/补码方式"NoPadding PkcsPadding
            Cipher cipher = Cipher.getInstance("AES/CBC/Nopadding");
            int blockSize = cipher.getBlockSize();

            byte[] dataBytes = data.getBytes();
            int plaintextLength = dataBytes.length;
            if (plaintextLength % blockSize != 0) {
                plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
            }

            byte[] plaintext = new byte[plaintextLength];
            System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);

            SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(), "AES");
            IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes());

            cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
            byte[] encrypted = cipher.doFinal(plaintext);

            return DatatypeConverter.printBase64Binary(encrypted);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 解密方法
     */
    private static String decrypt(String  data, String key, String iv) {
        try {
            byte[] encrypted = DatatypeConverter.parseBase64Binary(data);
            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
            IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes());
            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
            byte[] original = cipher.doFinal(encrypted);
            return new String(original).trim();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

13、统一消息返回体工具类 JsonResult

public class JsonResult{
    public static final int SUCCESS = 0;
    public static final int FAILURE = -1;
    public static final int KEEP = 1;
    private int result;
    private Long total;
    private String hint;
    private Object data;
    public JsonResult() {
        super();
    }
    public JsonResult(int result, Object data, Long total, String hint) {
        super();
        this.result = result;
        this.total = total;
        this.hint = hint;
        this.data = data;
    }
    /**
     * 操作成功,无返回数据
     * @return
     */
    public static JsonResult success() {
        return new JsonResult(SUCCESS, "无返回数据", null, "操作成功");
    }
    /**
     * 操作成功,返回data数据
     * @param data
     * @return
     */
    public static JsonResult success(Object data) {
        return new JsonResult(SUCCESS, data, null, "操作成功");
    }
    /**
     * 操作成功,返回data数据及数量
     */
    public static JsonResult success(Object data, Long total) {
        return new JsonResult(SUCCESS, data, total, "操作成功");
    }

    /**
     * 业务错误
     */
    public static JsonResult keep(String hint) {
        return new JsonResult(KEEP, null, null, hint);
    }
    /**
     * 程序错误
     */
    public static JsonResult failure(String hint, Exception e) {
        return new JsonResult(FAILURE, null, null, hint);
    }

    public int getResult() {
        return result;
    }

    public void setResult(int result) {
        this.result = result;
    }

    public Long getTotal() {
        return total;
    }

    public void setTotal(Long total) {
        this.total = total;
    }

    public String getHint() {
        return hint;
    }

    public void setHint(String hint) {
        this.hint = hint;
    }

    public Object getData() {
        return data;
    }

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

14、实体 UsersDomain 字段注解偷懒没加全

import org.hibernate.annotations.GenericGenerator;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

@Entity
@Table(name = "USERS")
public class UsersDomain implements Serializable, UserDetails {
    private static final long serialVersionUID = 629746947476582411L;
    /**
     * id
     */
    @Id
    @GeneratedValue(generator = "idGenerator", strategy = GenerationType.IDENTITY)
    @GenericGenerator(name = "idGenerator", strategy = "uuid")
    @Column(name = "ID", nullable = false, length = 32)
    private String id;
    /**
     * 用户名
     */
    private String userName;
    /**
     * 密码
     */
    private String password;
    /**
     * 真实姓名
     */
    private String realName;
    /**
     * 账号
     */
    private String account;
    /**
     * 账号是否可用 默认为1 可用
     */
    private String isEnabled;
    /**
     * 创建时间
     */
    private Date createTime;
    /**
     * 修改人
     */
    private String createUser;

    @OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.REMOVE})
    @JoinTable(name = "users_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id"))
    private List<RoleDomain> roles;


    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for (RoleDomain role : roles) {
            authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getRoleName()));
        }
        return authorities;
    }

    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return 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;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getRealName() {
        return realName;
    }

    public void setRealName(String realName) {
        this.realName = realName;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public String getIsEnabled() {
        return isEnabled;
    }

    public void setIsEnabled(String isEnabled) {
        this.isEnabled = isEnabled;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public String getCreateUser() {
        return createUser;
    }

    public void setCreateUser(String createUser) {
        this.createUser = createUser;
    }

    public List<RoleDomain> getRoles() {
        return roles;
    }

    public void setRoles(List<RoleDomain> roles) {
        this.roles = roles;
    }
}

15、实体 RoleDomain 字段注解偷懒没加全

import org.hibernate.annotations.GenericGenerator;
import org.springframework.security.core.GrantedAuthority;
import javax.persistence.*;
import java.io.Serializable;

@Table(name = "ROLE")
@Entity
public class RoleDomain implements Serializable, GrantedAuthority {
    private static final long serialVersionUID = 620648570340911080L;
    /**
     * id
     */
    @Id
    @GeneratedValue(generator = "idGenerator")
    @GenericGenerator(name = "idGenerator", strategy = "uuid")
    @Column(name = "ID", nullable = false, length = 32)
    private String id;
    /**
     * 角色名
     */
    private String roleName;
    /**
     * 角色说明
     */
    private String roleDescription;

    @ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE})
    @JoinTable(name = "users_role", joinColumns = @JoinColumn(name = "role_id"), inverseJoinColumns = @JoinColumn(name = "user_id"))
    private UsersDomain users;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public String getRoleDescription() {
        return roleDescription;
    }

    public void setRoleDescription(String roleDescription) {
        this.roleDescription = roleDescription;
    }

    public UsersDomain getUsers() {
        return users;
    }

    public void setUsers(UsersDomain users) {
        this.users = users;
    }

    @Override
    public String getAuthority() {
        return this.roleName;
    }
}

补充:具体实现类方法不粘了

二、前台

1、main.js

import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import router from './router/index'
import axios from 'axios'
import {Message} from 'element-ui';
import store from "@/store/store";
// 配置公共url
axios.defaults.baseURL = '/api'
//添加请求拦截器
axios.interceptors.request.use(
    config =>{
        if(store.state.token){
            config.headers.common['token'] =store.state.token
        }
        return config;
    },
    error =>{
        //对请求错误做什么
        return Promise.reject(error);
    })

//http reponse响应拦截器
axios.interceptors.response.use(
    response =>{
        return response;
    },
    error=>{
        if(error.response){
            switch(error.response.status){
                case 401:
                    localStorage.removeItem('token');
                    router.replace({
                        path: '/login',
                        query: {redirect: router.currentRoute.fullPath}//登录成功后跳入浏览的当前页面
                    })
            }
        }
    });
Vue.config.productionTip = false
Vue.use(ElementUI)
Vue.prototype.$EventBus = new Vue();
Vue.prototype.$http = axios;
Vue.prototype.$message = Message;

new Vue({
  render: h => h(App),
  router,
    store
}).$mount('#app')

2、router/index.js

import Vue from "vue";
import Router from "vue-router"
Vue.use(Router);
const originalPush = Router.prototype.push
Router.prototype.push = function push(location) {
    return originalPush.call(this, location).catch(err => err)
}
const layout = () => import('../views/layout/layout')
const router = new Router({
    routes: [
        {
            path: "/",
            name: "login",
            component: resolve => require(["@/views/login/index"], resolve),
            meta: {
                keepAlive: true,
                mainPart: "登录",
                secondaryPart: "登录"
            }
        },
        {
            // 首页
            path: "/homePage",
            name: "homePage",
            component: resolve => require(["@/views/homePage"], resolve),
            meta: {
                keepAlive: true,
                mainPart: "首页",
                secondaryPart: "首页"
            }
        },
        {
            path: "/login",
            name: "login",
            component: resolve => require(["@/views/login/index"], resolve),
            meta: {
                keepAlive: true,
                mainPart: "登录",
                secondaryPart: "登录"
            }
        }
    ]
});

// 导航守卫
// 使用router.beforeEach注册一个全局前置守卫,判断用户是否登录
router.beforeEach((to, from, next) => {
    if (to.path === '/login' || to.path === '/') {
        localStorage.removeItem('token');
        next();
    } else {
        let token = localStorage.getItem('token');
        if (token === null || token === '') {
            next('/login');
        } else {
            next();
        }
    }
});

export default router;

3、store/index.js

//  store.js 中都mutation中增加添加和删除token的方法
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

const state = {     // 全局管理的数据存储
    isLogin:'0',
    ser:null,
    token:localStorage.getItem('token') ? localStorage.getItem('token'):'',   // token
};
export default new Vuex.Store({
    state,
    getters:{    // 监听数据变化的
        getStorage(state){   // 获取本地存储的登录信息
            if(!state.token){
                // state.token =JSON.parse(localStorage.getItem(key))
                state.token =JSON.parse(localStorage.getItem('token'))
            }
            return state.token
        }
    },
    mutations:{
        $_setToken(state, value) { // 设置存储token
            state.token = value;
            localStorage.setItem('token', value);
        },
        $_removeStorage(state, value){  // 删除token
            localStorage.removeItem('token');
        },
    }
});

4、AES加密解密方法utils/secret.js

import CryptoJs from 'crypto-js/crypto-js'

const KEY = CryptoJs.enc.Utf8.parse("aaDJL2d9DfhLZO0z");
const IV = CryptoJs.enc.Utf8.parse("412ADDSSFA342442");

// AES加密:字符串 key iv 返回base64

export function Encrypt(word, keyStr, ivStr) {
    let key = KEY;
    let iv = IV;
    if (keyStr) {
        key = CryptoJs.enc.Utf8.parse(keyStr);
        iv = CryptoJs.enc.Utf8.parse(ivStr);
    }
    let srcs = CryptoJs.enc.Utf8.parse(word);
    var encrypted = CryptoJs.AES.encrypt(srcs, key, {
        iv: iv,
        mode: CryptoJs.mode.CBC,
        padding:CryptoJs.pad.ZeroPadding
    });
    return CryptoJs.enc.Base64.stringify(encrypted.ciphertext)
}

/**
 * 解密
 * @param word
 * @param keyStr
 * @param ivStr
 * @returns {string}
 * @constructor
 */
export function Decrypt(word, keyStr, ivStr) {
    let key = KEY;
    let iv = IV;
    if (keyStr) {
        key = CryptoJs.enc.Utf8.parse(keyStr);
        iv = CryptoJs.enc.Utf8.parse(ivStr);
    }
    let base64 = CryptoJs.enc.Base64.parse(word);
    let src = CryptoJs.enc.Base64.stringify(base64);
    let decrypt = CryptoJs.AES.decrypt(src, key, {
        iv: iv,
        mode: CryptoJs.mode.CBC,
        padding:CryptoJs.pad.ZeroPadding
    });
    let decryptedStr = decrypt.toString(CryptoJs.enc.Utf8);
    return decryptedStr.toString();

}

5、登录页 login/index

<el-form :model="loginForm">
    <el-form-item label="用户名" prop="username">
        <el-input v-model="loginForm.userName"></el-input>
    </el-form-item>
    <el-form-item label="密码" prop="password">
        <el-input type="password" v-model="loginForm.password"></el-input>
    </el-form-item>
    <el-form-item>
        <el-button @click="login('loginForm')">登录</el-button>
    </el-form-item>
</el-form>
import {Encrypt} from "@/utils/secret";

export default {
    name: "index",
    data() {
        return {
            loginForm:{
                userName:"",
                password:""
            }
        }
    },
    methods:{
        login() {
            const that = this;
            this.$http.post('/login', {
                username:this.loginForm.userName,
                //密码加密传输
                password:Encrypt(this.loginForm.password)
            }, {headers:{"Content-Type":"application/x-www-form-urlencoded"}, transformRequest: [function (data) {
                            let ret = ''
                            for (let it in data) {
                                ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
                            }
                            return ret
                        }]
                ,}).then(function (res) {
                if (res.data.result === 0) {
                    that.$message.success("登录成功!");
                    that.$store.commit('$_setToken', res.headers.token);
                    that.$router.push('/homePage');
                } else {
                    that.$message.error("登录失败");
                }
            })
        }
    }
}