springboot+springsecurity+mybatis+jwt实现单点登录(详细到爆了)

项目下载:https://download.csdn.net/download/Kevinnsm/16751962



Ⅰ、jwt是什么

JWT,全称是Json Web Token, 是JSON风格轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权。

Ⅱ、为什么要使用jwt

互联网认证方式由两种:无状态和有状态

2.1、什么是有状态

即服务端需要记录每次会话的客户端信息,典型的如session

有状态流程

1.当用户登录后,客户端会保存该用户的信息到服务端的session中,然后返回一个sessionId,将其保存到客户端的Cookie中

2.当下次用户访问时携带者Cokkie值,这样服务端就能识别对应的session

缺点

1.服务端保存大量用户状态信息,增大了服务端的压力
2.服务端保存用户状态信息,无法进行水平扩展(用户状态信息只保存在这一个服务器中,访问其他服务器还需要重新登录认证)

简单了说有状态就是服务端需要保存用户状态信息

2.2、什么是无状态

服务端不需要保存任何客户端的用户状态信息
我是刘松林的而

无状态流程

1.用户第一次访问时,服务端要求用户进行身份认证(登录)
2.用户认证成功后,服务端返回一个token,返回给客户端作为令牌
3.当用户再次访问时需要携带该token(令牌),服务端对令牌进行解密判断

在这里插入图片描述

优点

1.减少了服务端的压力
2.服务端可以进行任意的伸缩
3.用户登录后可以向多个服务器进行访问而不用进行二次登录

当然无状态最重要的就是保证token的安全性

Ⅲ、jwt的组成

Jwt由三部分组成

1、header

header由两部分组成

1、声明类型:这里是jwt
2、签名算法:比如SHA256、SHA512等

header经过base64编码之后就形成了jwt第一部分

2、Payload(载荷)

payload是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据

iss:发行人
exp:到期时间
sub:主题

经过base64编码之后形成了jwt的第二部分

3、Signature(签名)

一般根据前两步的数据,再加上服务的的密钥(secret),通过header里面声明的加密算法生成jwt的第三部分。

secret是服务端用来进行jwt的签发和校验,所以说secret非常重要,任何时候都不应该泄露jwt,一旦jwt泄露,意味着恶意用户可以自我签发jwt了。


三、springboot+springsecurity+mybatis+jwt实现单点登录

Ⅰ、核心流程分析

如果使用springsecurity进行过认证操作,那么这下面将会对你来说很简单

1、根据前述的无状态概念,当用户登录成功后,我们需要在服务端进行token的生成(当然这里是使用jwt生成token);然后当用户进行再次访问时,我们需要对token进行解析、判断等;所以我们可以将token的创建、解析、判断等封装到一个工具类JwtTokenUtil中。

2、我们还需要自定义一个AuthenticationFilter过滤器用来拦截请求;判断请求中是否携带token,如果携带了token,那就进行解析判断;如果没有token,则可能是首次登录,所以可以放行,交给springsecurity进行认证(也就是交给UsernamePasswordAuthenticationFilter过滤器)

3、第二步中有一个关键点,我们自定义的AuthenticationFilter过滤器应该加到哪个地方,我们都知道springsecurity本质上是一串过滤器链,将请求进行层层拦截、判断、放行或者不放行。根据第二步的分析我们可以将AuthenticationFilter过滤器加入到UsernamePasswordAuthenticationFilter过滤器前面,这样一来第二步就行很清晰了。

其他就类似于springsecurity进行认证和授权的步骤了

Ⅱ、具体代码实现及其分析

1.JwtTokenUtil

package com.jwt.utils;



import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author:抱着鱼睡觉的喵喵
 * @date:2021/4/13
 * @description:
 */
public class JwtTokenUtils {
    /**
     * 请求头
     */
    private static final String TOKEN_HEADER = "Authorization";
    /**
     * TOKEN 前缀
     */
    public static final String TOKEN_PREFIX = "Bearer";
    /**
     * 私钥
     */
    private static final String TOKEN_SECRET = "secret";
    /**
     * 令牌过期时间 : one day
     */
    private static final long TOKEN_EXPIRATION = 1000 * 60 * 60 * 24;
    /**
     * 角色权限定义
     */
    private static final String ROLE_CLAIMS = "role";

    /**
     * 创建令牌
     *
     * @param username
     * @param role
     */
    public static String createToken(String username, String role) {
        Map<String, Object> map = new HashMap<>();
        map.put(ROLE_CLAIMS, role);
        return Jwts.builder()
                .setClaims(map)
                .setSubject(username)
                .claim("username", username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + TOKEN_EXPIRATION))
                .signWith(SignatureAlgorithm.HS256, TOKEN_SECRET)
                .compact();
    }

    /**
     * 获取解析后的token信息
     * @param token
     * @return
     */
    public static Claims getTokenBody(String token) {
        return parseToken(token).getBody();
    }

    /**
     * 检查token是否存在
     * @param token
     * @return
     */
    public static Claims checkToken(String token) {

        try {
            Claims claims = getTokenBody(token);
            return claims;
        } catch (Exception e) {
            return null;
        }
    }
    /**
     * 判断令牌是否过期
     * @param token 令牌
     * @return boolean
     * @describe getExpiration()获取令牌过期时间
     */
    public static boolean isExpiration(String token) {
        return getTokenBody(token).getExpiration().before(new Date());
    }

    /**
     * 解析令牌
     * @param token
     * @return
     */
    public static Jws<Claims> parseToken(String token) {
        return Jwts.parser().setSigningKey(TOKEN_SECRET).parseClaimsJws(token);
    }
    /**
     * 解析token,获取用户名
     */
    public static String parseTokenToUsername(String token) {
        String username = getTokenBody(token).getSubject();
        return username;
    }
}



2.统一结果返回封装类

使用泛型,因为在实际的场景中可能由多种不同的数据要进行返回

package com.jwt.utils;

import lombok.Data;

import java.io.Serializable;

/**
 * @author:抱着鱼睡觉的喵喵
 * @date:2021/4/12
 * @description:
 */
@Data
public  class ResponseBody<T> implements Serializable {

    private Integer code;
    private String msg;
    private T data;

    public ResponseBody(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    public ResponseBody(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
}

3、handler处理类

这些处理类将在SecurityConfig配置类中进行配置,所以需要先将每个handler处理类放到spring容器中====》@Component注解

3.1、AjaxAccessDeniedHandler权限不足处理类
package com.jwt.handler;

import com.alibaba.fastjson.JSON;
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;

/**
 * @author:抱着鱼睡觉的喵喵
 * @date:2021/4/12
 * @description: 权限不足处理类
 */
@Component
public class AjaxAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        response.getWriter().write(JSON.toJSONString("权限不足"));
    }
}

3.2、AjaxAuthenticationEntryPoint匿名无权限处理类

当用户匿名访问资源时(即不登陆去访问资源),会跳转到登录页面

package com.jwt.handler;

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;

/**
 * @author:抱着鱼睡觉的喵喵
 * @date:2021/4/14
 * @description: 匿名未认证登录处理类
 */
@Component
public class AjaxAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.setContentType("application/json;charset=utf-8");
        request.getRequestDispatcher("/login.html").forward(request,response);
    }
}

3.3、AjaxAuthenticationFailureHandler认证失败处理类

当用户登录失败时,会抛出相应的异常,然后交给该类进行判断是哪一种异常。

package com.jwt.handler;


import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

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

/**
 * @author:抱着鱼睡觉的喵喵
 * @date:2021/4/11
 * @description:    认证失败Handler处理类
 */
@Slf4j			//日志
@Component
public class AjaxAuthenticationFailureHandler implements AuthenticationFailureHandler {
    private String url;

    public AjaxAuthenticationFailureHandler(){}
    public AjaxAuthenticationFailureHandler(String url) {
        this.url = url;
    }

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

        String failData = "";
        if (exception instanceof AccountExpiredException) {
            failData = "账号过期";
        } else if (exception instanceof UsernameNotFoundException) {
            failData = "账号不存在";
        } else if (exception instanceof CredentialsExpiredException) {
            failData = "密码过期";
        } else if (exception instanceof DisabledException) {
            failData = "账号不可用";
        } else if (exception instanceof LockedException) {
            failData = "账号锁定";
        } else if (exception instanceof BadCredentialsException) {
            failData = "密码错误";
        } else {
            failData = "未知异常";
        }
        log.info("认证失败,"+failData);
        //设置编码格式,否则中文会乱码
        response.setCharacterEncoding("utf-8);
        response.setContentType("application/json;charset=utf-8");
        //返回统一数据
        response.getWriter().write(JSON.toJSONString(failData));
    }
}

3.4、AjaxAuthenticationSuccessHandler认证成功处理类

当用户认证成功后,将交与该处理类处理;返回token(前缀带bearer,所以服务端在进行解析token时,应该将bearer去掉。

package com.jwt.handler;

import com.alibaba.fastjson.JSON;
import com.jwt.entity.User;
import com.jwt.utils.JwtTokenUtils;
import com.jwt.utils.ResponseBody;
import org.springframework.security.core.Authentication;
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;

/**
 * @author:抱着鱼睡觉的喵喵
 * @date:2021/4/11
 * @description:    认证成功Handler处理类
 */
@Component
public class AjaxAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    private String url;

    public AjaxAuthenticationSuccessHandler() {
    }

    public AjaxAuthenticationSuccessHandler(String url) {
        this.url = url;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        User user = (User) authentication.getPrincipal();
        String token = JwtTokenUtils.createToken(user.getUsername(), String.valueOf(user.getAuthorities()));
        //设置编码,如果不设置会乱码
        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json;charset=utf-8");
        //设置返回的token 带有Bearer的前缀字符串
        response.setHeader("Authorization",  JwtTokenUtils.TOKEN_PREFIX+token);
        response.getWriter().write(new ResponseBody<User>(200,"登录成功",user).toString());
    }
}

3.5、AjaxLogoutSuccessHandler退出成功处理类

当用户退出登录成功时,跳转到登录页面

package com.jwt.handler;

import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;

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

/**
 * @author:抱着鱼睡觉的喵喵
 * @date:2021/4/16
 * @description:
 */
@Component
@Slf4j
public class AjaxLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        request.getRequestDispatcher("/login.html").forward(request,response);
    }
}

4、AuthenticationFilter(token的验证类)

该类对请求进行拦截,配置到UsernamePasswordAuthenticationFilter过滤器类的前面,至于功能前面已经分析过。

Spring Security使用一个Authentication对象来描述当前用户的相关信息。SecurityContextHolder中持有的是当前用户的SecurityContext,而SecurityContext持有的是代表当前用户相关信息的Authentication的引用。这个Authentication对象不需要我们自己去创建,在与系统交互的过程中,Spring Security会自动为我们创建相应的Authentication对象,然后赋值给当前的SecurityContext,我们经常会使用SecurityContextHolder获取SecurityContext实例,然后获取Authentication实例。下面会是使用到

package com.jwt.filter;
import com.jwt.entity.User;
import com.jwt.service.LoginUserDetailService;
import com.jwt.utils.JwtTokenUtils;
import org.springframework.beans.factory.annotation.Autowired;
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.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
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;

@Component
public class AuthenticationFilter extends OncePerRequestFilter {
    @Autowired
    LoginUserDetailService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    //获取请求头中的token加密串(名字是Authorization,JwtTokenUtils工具类里面定义的
        String authHeader = request.getHeader("Authorization");
//判断是否是以Bearer开头的,这是我自定义的前缀名)
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
        //将Bearer去掉,因为返回token时,我在token前缀加入了Bearer字符串
            String authToken = authHeader.substring("Bearer ".length());
//解析加密串,获取用户名
            String username = JwtTokenUtils.parseTokenToUsername(authToken);
            //看当前SecurityContext中是否有Authentication实例(前面已经介绍过)
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
//如果Authentication没有当前用户的信息,然后从数据库中查询出相应的信息
                User user = (User) userDetailsService.loadUserByUsername(username);
                if (user != null) {
                //封装到UsernamePasswordAuthenticationToken令牌类中
                    UsernamePasswordAuthenticationToken authentication =
                            new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
//将用户信息放到Authenticatin实例中进行存储
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }

        filterChain.doFilter(request, response);
    }
}


这个过滤器类里面是jwt最核心的点
首先请求进入到该过滤器类时,获取token串,进行解析;然后查看Authentication是否为null,如果为null,根据解析到的用户名去数据库查询相应的信息,一并将其放到Authentication实例中。
当你下一次再进行token判断时,直接从Authentication拿取相应信息即可,不需要再查询数据库。

5、SecurityConfig配置类

package com.jwt.config;
import com.jwt.filter.AuthenticationFilter;
import com.jwt.handler.*;
import com.jwt.service.LoginUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.annotation.Resource;
import javax.sql.DataSource;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private LoginUserDetailService userDetailsService;
    @Autowired
    private AjaxAccessDeniedHandler deniedHandler;
    @Autowired
    private AjaxAuthenticationEntryPoint ajaxAuthenticationEntryPoint;
    @Autowired
    private AjaxAuthenticationSuccessHandler successHandler;
    @Autowired
    private AjaxAuthenticationFailureHandler failureHandler;
    @Autowired
    private DataSource dataSource;
    @Autowired
    private AuthenticationFilter authenticationFilter;
    @Autowired
    private AjaxLogoutSuccessHandler logoutSuccessHandler;
    /**
     * 拦截策略
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().and()
                .formLogin().loginPage("/login.html")
                .loginProcessingUrl("/login")
                .successHandler(successHandler)
                .failureHandler(failureHandler)
                .and()
                .authorizeRequests().antMatchers("/login.html","/register.html","/register","/js/**").permitAll()
                .and()
                .exceptionHandling()
                .accessDeniedHandler(deniedHandler)
                .authenticationEntryPoint(ajaxAuthenticationEntryPoint)
                .and().authorizeRequests()
                .anyRequest().authenticated();
        http.addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter.class);
        //设置记住我
        http.rememberMe().tokenRepository(persistentTokenRepository())
                .tokenValiditySeconds(600)
                .userDetailsService(userDetailsService);
  //配置退出登录操作
        http.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
        //关闭csrf防护
        http.csrf().disable();
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    /**
     * 配置忽略的URL
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/");
    }

    /**
     * 拦截后需要使用自定义的类和加密解密方式
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(daoAuthenticationProvider());
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());

    }


    @Override
    @Bean
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    /**
     * 
     * @return HideUserNotFoundExceptions(false),否则UsernameNotFoundException异常会被BadCredentialsException异常覆盖
     */
    @Bean
    public AuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        daoAuthenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
        daoAuthenticationProvider.setHideUserNotFoundExceptions(false);
        return daoAuthenticationProvider;
    }

//这是spirngsecurity记住我功能需要加入代码
//JdbcTokenRepositoryImpl是将其保存到数据库中
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
    //这个是记住我保存到数据库的类,当然还有保存到内存的类
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        //设置数据源
        tokenRepository.setDataSource(dataSource);
 //这个会在第一次使用记住我时在数据库中创建一张表(用户、过期时间等)
 //在第二次一定要将其注释掉,否者会报错。
 //       tokenRepository.setCreateTableOnStartup(true);
        return tokenRepository;
    }
}

6、自定义异常处理类

6.1、RegisterFailureException注册失败异常处理类
package com.jwt.exception;

/**
 * @author:抱着鱼睡觉的喵喵
 * @date:2021/4/14
 * @description:
 */
public class RegisterFailureException extends Exception{

    private String message;
    public RegisterFailureException() {
        super();
    }
    public RegisterFailureException(String message) {
       this.message = message;
    }
}

6.2、RegisterUsernameHasBeenExists注册时用户名已经存在异常处理类
package com.jwt.exception;

/**
 * @author:抱着鱼睡觉的喵喵
 * @date:2021/4/15
 * @description:
 */
public class RegisterUsernameHasBeenExists extends Exception{
    private String message;
    public RegisterUsernameHasBeenExists(String message) {
        this.message = message;
    }
}

6.3、Controller层全局异常处理类
package com.jwt.exception.controllerException;

import com.jwt.exception.RegisterUsernameHasBeenExists;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.sql.SQLException;
import java.util.Objects;

/**
 * @author:抱着鱼睡觉的喵喵
 * @date:2021/4/15
 * @description:
 */
@Slf4j
@RestControllerAdvice
public class ControllerHandlerExceptionAdvice {

    private static final Logger logger = LoggerFactory.getLogger(ControllerHandlerExceptionAdvice.class);
    /**
     * 拦截表单参数异常处理
     * @param exception
     * @param request
     * @return
     */
    @ResponseStatus(HttpStatus.OK)
    @ExceptionHandler({BindException.class})
    public String bindException(BindException exception, HttpServletRequest request) {
        logger.info("表单拦截校验处理=====>");
        logger.info("表单数据======>"+request.getContentType());
        BindingResult bindingResult = exception.getBindingResult();
        return Objects.requireNonNull(bindingResult.getFieldError().getDefaultMessage());
    }

    @ExceptionHandler
    public String handler(HttpServletRequest request, HttpServletResponse response, Exception e) {
        logger.info("RestFul 请求发生异常........");
        if (response.getStatus() == HttpStatus.BAD_REQUEST.value()) {
            logger.info("状态值不是200,正准备修改为200");
            response.setStatus(HttpStatus.OK.value());
        }
        if (e instanceof NullPointerException) {
            logger.error("发生了空指针异常======》",e.getMessage());
            return "空指针异常";
        } else if (e instanceof IllegalArgumentException) {
            logger.error("请求参数不匹配异常======>",e.getMessage());
            return "请求参数不匹配";
        } else if (e instanceof SQLException) {
            logger.error("数据库访问异常======>",e.getMessage());
            return "数据库访问异常";
        } else if (e instanceof BindException) {
            BindingResult bindingResult = ((BindException) e).getBindingResult();
            logger.error("表单校验异常",bindingResult.getFieldError().getDefaultMessage());
            return Objects.requireNonNull(bindingResult.getFieldError().getDefaultMessage());
        } else if (e instanceof RegisterUsernameHasBeenExists) {
            logger.info("用户名已经存在");
            return "用户名已经存在";
        } else {
            logger.error("未知异常",e.getMessage());
            return "服务器端未知异常,请去检查!";
        }
    }
}

7、controller,service,mapper,entity,mapper.xml编写

这几个类就是简单的crud了,没什么可说的

7.1、controller
package com.jwt.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author:抱着鱼睡觉的喵喵
 * @date:2021/4/7
 * @description:
 */
@Controller
public class LoginController {

    @RequestMapping("/toMain")
    public String main() {
        return "redirect:success.html";
    }

    @RequestMapping("/")
    public String demo() {
        return "main";
    }
    @RequestMapping("/logout")
    public String logout() {
        return "redirect:login.html";
    }
}

package com.jwt.controller;

import com.jwt.entity.User;
import com.jwt.entity.UserDto;
import com.jwt.exception.RegisterFailureException;
import com.jwt.exception.RegisterUsernameHasBeenExists;
import com.jwt.service.RegisterService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author:抱着鱼睡觉的喵喵
 * @date:2021/4/14
 * @description:
 */
@Controller
@Slf4j
public class RegisterController {
    @Autowired
    private RegisterService registerService;

    @RequestMapping("/register")
    public String register(@Validated UserDto userDto) throws RegisterUsernameHasBeenExists {
        log.info("注册的数据为:"+userDto.toString() );

        User user = registerService.selectDup(userDto.getUsername());
        if (user == null) {
            throw new RegisterUsernameHasBeenExists("用户已存在");
        }
        try {
            registerService.register(userDto);
        } catch (RegisterFailureException e) {
            e.printStackTrace();
        }
        return "/";
    }
}

package com.jwt.controller;

import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.web.bind.annotation.*;

/**
 * @author:抱着鱼睡觉的喵喵
 * @date:2021/4/15
 * @description:
 */
@RestController
@RequestMapping("/api")
@EnableWebSecurity
public class TestController {

    @RequestMapping("/add")
    public String add() {
        return "add";
    }

    @RequestMapping("select")
    public String select() {
        return "select";
    }

    @RequestMapping("/delete")
    public String delete() {
        return "delete";
    }

    @RequestMapping("/update")
    public String update() {
        return "update";
    }
}

7.2、service
package com.jwt.service;

import com.jwt.entity.User;
import com.jwt.entity.UserDto;
import com.jwt.exception.RegisterFailureException;
import com.jwt.exception.RegisterUsernameHasBeenExists;

/**
 * @author:抱着鱼睡觉的喵喵
 * @date:2021/4/14
 * @description:
 */
public interface RegisterService {
    boolean register(UserDto userDto) throws RegisterFailureException;
    User selectDup(String username) throws  RegisterUsernameHasBeenExists;
}

这个LoginUserDetailService是要实现UserDetailService,根据用户名查询数据数据库用户信息,将其交给SpringSecurity进行认证

至于为什么要这样:https://blog.csdn.net/Kevinnsm/article/details/115189976

package com.jwt.service;


import com.jwt.entity.User;
import com.jwt.mapper.LoginMapper;
import lombok.extern.slf4j.Slf4j;
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.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.Service;

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

/**
 * @author:抱着鱼睡觉的喵喵
 * @date:2021/4/7
 * @description:   根据传来的username去数据库中查询用户信息,然后交给springsecurity去认证
 */
@Service
@Slf4j
public class LoginUserDetailService implements UserDetailsService {

    @Autowired
    private LoginMapper loginMapper;
    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
     *
     * 自定义认证
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if (username == null || username.equals("")) {
            throw new RuntimeException("用户名不能为空!");
        }
        User user = loginMapper.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户不能为空!");
        }
        log.info("根据username查询的用户信息为===>"+user.toString() + "==》等待认证!");
        List<GrantedAuthority> authorities = new ArrayList<>();
//        loginMapper.findAuthorityByUsername(username).forEach(role->
//            authorities.add(new SimpleGrantedAuthority((String) role))
//        );
        authorities.add(new SimpleGrantedAuthority(loginMapper.findAuthorityByUsername(username)));
        user.setAuthorities(authorities);
        log.info(user.getUsername()+"用户的所有权限为权限===>"+authorities.toString());
        return new User(user.getUsername(), passwordEncoder.encode(user.getPassword()),authorities);
    }

}

package com.jwt.service.impl;

import com.jwt.entity.User;
import com.jwt.entity.UserDto;
import com.jwt.exception.RegisterFailureException;
import com.jwt.exception.RegisterUsernameHasBeenExists;
import com.jwt.mapper.RegisterMapper;
import com.jwt.service.RegisterService;
import com.sun.deploy.association.RegisterFailedException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author:抱着鱼睡觉的喵喵
 * @date:2021/4/14
 * @description:
 */
@Service
@Slf4j
public class RegisterServiceImpl implements RegisterService {

    @Autowired
    private RegisterMapper registerMapper;
    @Override
    public boolean register(UserDto userDto) throws RegisterFailureException {
        boolean b = registerMapper.register(userDto);
        System.out.println(b);
        if (!b) {
            log.info("注册失败");
            throw new RegisterFailureException("注册失败");
        }
        return b;
    }

    @Override
    public User selectDup(String username) throws  RegisterUsernameHasBeenExists {
        User user = registerMapper.selectDup(username);
        if (user != null) {
            throw new RegisterUsernameHasBeenExists("用户名已经存在");
        }
        return null;
    }
}

7.4、mapper
@Repository
public interface LoginMapper {
    User findByUsername(String username);
    String findAuthorityByUsername(String username);
}
@Repository
public interface RegisterMapper {
    boolean register(UserDto userDto);
    User selectDup(String username);
}

7.5、mapper.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.jwt.mapper.LoginMapper">
    <select id="findByUsername" resultType="User" parameterType="string">
        select * from user where username = #{username} limit 1
    </select>
    <select id="findAuthorityByUsername" resultType="string">
        select authority from role where username = #{username} limit 1
    </select>
</mapper>

<?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.jwt.mapper.RegisterMapper">
    <insert id="register" parameterType="userDto">
        insert  into user (username,password) values(#{username},#{password})
    </insert>
    <select id="selectDup" resultType="user">
        select *  from user where username=#{username} limit 1
    </select>
</mapper>
7.6、entity
@Data
@NoArgsConstructor
public class User implements UserDetails {
    private String username;
    private String password;
    private List<GrantedAuthority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }
    public User(String username, String password, List<GrantedAuthority> authorities) {
        this.username = username;
        this.password = password;
        this.authorities = authorities;
    }
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

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

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


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

@Data
public class Authority implements Serializable {
    private String roles;
}

package com.jwt.entity;

import com.sun.istack.internal.NotNull;
import lombok.Data;

import javax.validation.constraints.Size;
import java.io.Serializable;

/**
 * @author:抱着鱼睡觉的喵喵
 * @date:2021/4/14
 * @description:
 */
@Data
public class UserDto implements Serializable {

    @NotNull
    @Size(min = 3, max = 15, message = "用户名必须在3~15之间")
    private String username;

    @NotNull
    @Size(min = 5, max = 15, message = "密码必须在5~15之间")
    private String password;
}

8、application.yml

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/security?serverTimezone=UTC
    username: root
    password: hao20001010


mybatis:
  mapper-locations: mapper/**
  type-aliases-package: com.jwt.entity

server:
  port: 8081

9、html测试

templates模板下的main.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>这是主页面</h1> <br/>
    <a href="login.html">登录</a>
    <a href="register.html">注册</a>
    <br>
    <hr>
    <h2>操作如下</h2>
    <a href="/api/add">添加商品</a>
    <a href="/api/select">查询商品</a>
    <a href="/api/update">修改商品</a>
    <a href="/api/delete">删除商品</a>
    <a href="/logout">退出登录</a>

</body>
</html>

static文件下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>
<h2>=======登录页面=========</h2>
    <form action="/login" method="post">
        username:<input type="text" name="username"/><br/>
        password:<input type="password" name="password" /><br/>
        remember me:<input type="checkbox" name="remember-me" value="true"/><br/>
        <input type="submit" value="提交" />
    </form>
</body>

</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>=========注册页面=========</h2>
<form action="/register" method="post">
    <table>
        username:<input type="text" name="username"/><br/>
        password:<input type="password" name="password"/><br/>
        <input type="submit" value="提交"/>
    </table>
</form>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>分页面</h1><br>
<a href="/toMain">主页面</a><br>
<a href="/logout">退出</a>

</body>
</html>

到这里也就是结束了,测试截图就不再发了,Controller层应该使用Rest风格的。

回顾这个小demo,其实简单了分析就是:springsecurity是一串过滤器链,然后自定义jwt的校验token过滤器,最后将其加入到过滤器链中,至于加到什么位置,想必都明白了。

核心就前端那一段,其他的基本数就是springsecurity的基本操作和一些crud操作及其某些细节。把握住核心即可。

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thecoastlines

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值