Springboot+SpringSecurity+JWT +Vue实现前后端分离的登录 权限管理以及token管理

前言
使用SpringSecurity+jwt组合对用户的权限以及token进行管理,用户在成功登陆后,会分发一个token令牌,如果没有将某个接口进行权限设置,那么访问该页面时需要带着token令牌进行访问,后端将会对token令牌进行校验,如果校验通过则可以对该接口进行请求。

整体思路
1.导入springSecurity跟jwt的maven依赖
2.用户登录的实体类(这里就不详细说明mapper和service)
3.实现UserDetailsService接口
4.JwtUser实现UserDetails接口
5.JwtTokenUtils工具类
6.验证用户登录信息的拦截器(JWTAuthenticationFilter)
7.验证用户权限的拦截器(JWTAuthorizationFilter)
8.springSecurity配置
9.认证的Controller
10.前端vue进行测试(跨域可以在博主的其他文章中查看)

1.导入springSecurity跟jwt的maven依赖

	<!--springSecuriy-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        
	<!--jwt-->
	<dependency>
    		<groupId>io.jsonwebtoken</groupId>
    		<artifactId>jjwt</artifactId>
    		<version>0.9.1</version>
	</dependency>

2.用户登录的实体类

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="User对象", description="")
public class User  {

    private static final long serialVersionUID = 1L;

    @TableId(value = "uid", type = IdType.AUTO)
    private Long uid;

    private String username;

    private String password;

    private String role;
}

3.实现UserDetailsService接口
这里使用的是mybatisplus对数据库进行访问,在博主其他文章中也有笔记

@Service
public class UserServiceImpl  implements  UserDetailsService {

    @Autowired
    private UserMapper userMapper;
    // 用户登录逻辑和验证处理
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        //将用户传来的名字进行后缀判断角色
        JwtUser jwtUser = null;
        QueryWrapper<Student> wrapper = new QueryWrapper<>();
        wrapper.eq("username",s);
        User user = studentMapper.selectOne(wrapper);
	return new JwtUser(user);
    }
}

4.JwtUser实现UserDetails接口

public class JwtUser implements UserDetails {

    private Long id;
    private String username;
    private String password;
    private Collection<? extends GrantedAuthority> authorities;

    public JwtUser() {
    }

    public JwtUser(User user) {
        id = user.getUid();
        username = user.getUsername();
        password = user.getPassword();
        //这里说明一下,必须要加上ROLE_开头,或者在数据库直接以这个开头
        authorities = Collections.singleton(new SimpleGrantedAuthority("ROLE_"+user.getRole()));
    }

    // 获取权限信息
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    // 账号是否未过期,默认是false,记得要改一下
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    // 账号是否未锁定,默认是false,记得也要改一下
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    // 账号凭证是否未过期,默认是false,记得还要改一下
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

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

 }

5.JwtTokenUtils工具类
用于设置token过期时间,以及token的前缀等

public class JwtTokenUtils {
    public static final String TOKEN_HEADER = "Authorization";
    public static final String TOKEN_PREFIX = "Bearer ";
    private static final String SECRET = "jwtsecretdemo";
    private static final String ISS = "echisan";

    // 角色的key
    private static final String ROLE_CLAIMS = "rol";

    // 过期时间是3600秒,既是1个小时
    private static final long EXPIRATION = 3600L;

  
    // 创建token
    public static String createToken(String username,String role) {

        long expiration =EXPIRATION;
        HashMap<String, Object> map = new HashMap<>();
        map.put(ROLE_CLAIMS, role);
        return Jwts.builder()
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .setClaims(map)
                .setIssuer(ISS)
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
                .compact();

    }

    // 从token中获取用户名
    public static String getUsername(String token){
        return getTokenBody(token).getSubject();
    }

    // 获取用户角色
    public static String getUserRole(String token){
        return (String) getTokenBody(token).get(ROLE_CLAIMS);
    }

    // 是否已过期
    public static boolean isExpiration(String token) {
        try {
            return getTokenBody(token).getExpiration().before(new Date());
        } catch (ExpiredJwtException e) {
            return true;
        }
    }

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

}

6.验证用户登录信息的拦截器(JWTAuthenticationFilter)

//进行用户账号的验证
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    private AuthenticationManager authenticationManager;

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
        //设置登录请求的url
        super.setFilterProcessesUrl("/login");
    }

    @SneakyThrows
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,

          HttpServletResponse response) throws AuthenticationException{

        // 从输入流中获取到登录的信息
        try {
            LoginUser loginUser = new ObjectMapper().readValue(request.getInputStream(), LoginUser.class);
            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword(), new ArrayList<>()));

	//密码错误时抛出异常		
        }catch (BadCredentialsException b){
            System.out.println("密码错误");
            try {
                response.setCharacterEncoding("UTF-8");
                response.setContentType("application/json; charset=utf-8");
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                response.getWriter().write("密码错误");
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        //无此用户时抛出异常
        }catch (InternalAuthenticationServiceException i){
            System.out.println("没有此用户");
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write("xxx");
            return null;
        }

        catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    // 成功验证后调用的方法
    // 如果验证成功,就生成token并返回
    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {
        System.out.println("登录成功");
        JwtUser jwtUser = (JwtUser) authResult.getPrincipal();
        System.out.println("jwtUser:" + jwtUser.toString());
        String role = "";
        Collection<? extends GrantedAuthority> authorities = jwtUser.getAuthorities();
        for (GrantedAuthority authority : authorities) {
            role = authority.getAuthority();
        }
        String token = JwtTokenUt)ils.createToken(jwtUser.getUsername(), role);
        // 返回创建成功的token
        // 但是这里创建的token只是单纯的token
        // 按照jwt的规定,最后请求的时候应该是 `Bearer token`
        System.out.println(token);
        response.setHeader("token", JwtTokenUtils.TOKEN_PREFIX + token);
	//这里我还将该用户的id进行返回了
        response.setIntHeader("uid",jwtUser.getUid().intValue());
    }
    //配置失败返回的信息,当然成功的也可以返回思路一样,重写successful,这里我就不写了
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
	    System.out.println("用户登陆失败  AjaxAuthFailHandler");
	    response.setContentType("application/json;charset=utf-8");
	    response.setStatus(HttpStatus.UNAUTHORIZED.value());
	    PrintWriter out = response.getWriter();
	    out.write("{\"status\":\"error\",\"msg\":\"请检查用户名、密码或验证码是否正确\"}");
	    out.flush();
	    out.close();
    }
	
	
}

7.验证用户权限的拦截器(JWTAuthorizationFilter)
进行用户账号的验证(查看是否有token,token是否正确,token是否过时)

//进行用户账号的验证
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
    public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws IOException, ServletException {
        String tokenHeader = request.getHeader(JwtTokenUtils.TOKEN_HEADER);
        // 如果请求头中没有Authorization信息则直接放行了
        if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtils.TOKEN_PREFIX)) {
            chain.doFilter(request, response);
            return;
        }
        // 如果请求头中有token,则进行解析,并且设置认证信息
        try {
            SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
        } catch (TokenIsExpiredException e) {
            //返回json形式的错误信息
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            String reason = "统一处理,原因:" + e.getMessage();
            response.getWriter().write(new ObjectMapper().writeValueAsString(reason));
            response.getWriter().flush();
            return;
        }
        super.doFilterInternal(request, response, chain);
    }

    // 这里从token中获取用户信息并新建一个token
    private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) throws TokenIsExpiredException {
        String token = tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, "");
        boolean expiration = JwtTokenUtils.isExpiration(token);
        if (expiration) {
            throw new TokenIsExpiredException("token超时了");
        } else {
            String username = JwtTokenUtils.getUsername(token);
            String role = JwtTokenUtils.getUserRole(token);
            if (username != null) {
                return new UsernamePasswordAuthenticationToken(username, null,
                        Collections.singleton(new SimpleGrantedAuthority(role))
                );
            }
        }
        return null;
    }
}

8.springSecurity的配置

@Configuration //配置类
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConf extends WebSecurityConfigurerAdapter {
    @Autowired
    UserServiceImpl userService;

    //定义登陆成功返回信息
    private class AjaxAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            System.out.println("用户[" + SecurityContextHolder.getContext().getAuthentication().getPrincipal() +"]登陆成功!");
            response.setContentType("application/json;charset=utf-8");
            PrintWriter out = response.getWriter();
            out.write("{\"status\":\"ok\",\"msg\":\"登录成功\"}");
            out.flush();
            out.close();

        }
    }
    //定义登陆失败返回信息
    private class AjaxAuthFailHandler extends SimpleUrlAuthenticationFailureHandler {
        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
            System.out.println("用户登陆失败  AjaxAuthFailHandler");
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            PrintWriter out = response.getWriter();
            out.write("{\"status\":\"error\",\"msg\":\"请检查用户名、密码或验证码是否正确\"}");
            out.flush();
            out.close();
        }
    }

    //请求授权验
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // .denyAll();    //拒绝访问
        // .authenticated();    //需认证通过
        // .permitAll();    //无条件允许访问
        // 跨域一定要加上csrf();
        // 访问权限

        http.cors().and().csrf().disable().authorizeRequests()
                .antMatchers("/tologin","/islogin","/register","/registererr").permitAll()

                //.antMatchers("/User").hasRole("USER")
                // .antMatchers("/Admin").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
                .addFilter(new JWTAuthenticationFilter(authenticationManager()))
                .addFilter(new JWTAuthorizationFilter(authenticationManager()))
                 //不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);


         //登录配置
        http.formLogin()
                .successHandler(new AjaxAuthSuccessHandler())
                .failureHandler(new AjaxAuthFailHandler())
                //固定名字,其实默认也是username跟password,这里也可以不写
                .usernameParameter("username")
                .passwordParameter("password")
                //设置自己的登录界面(如果不设置,将会是自带的登录界面这里我们使用自定义的登录界面)
                .loginPage("/tologin")
                //表单提交的url
                .loginProcessingUrl("/login"); 

    }
    // 用户授权验证
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .userDetailsService(userService)
                .passwordEncoder(passwordEncoder()
                );

    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
        return source;
    }




    // 这里采用雪花加密方式对密码进行加密解密
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

9.认证的Controller

//单跨域的使用
@CrossOrigin
@Controller
public class PageController {
    @GetMapping("/tologin")
    public String tologin(){
        return "redirect:http://localhost:8001/Login";
    }
}

10.前端vue进行测试
这里需要有一点vue的基础, Vue这里不会详细讲用法了,但是会讲我是如何保存token,携带token发送请求的,这里我发送请求用的是axios,关于vue跨域配置等后期可能会写一篇相关的文章。
Login.vue
只给到了发送请求的地方,这里我将得到的token以及uid都存放到session中

var data = JSON.stringify(that.loginForm);
console.log(that.loginForm.username)           
this.$axios          
.post(url, data)          
.then(function (res) {            
	//登录成功后,将后端给的token以及uid都存在session中            
	window.sessionStorage.setItem("token", res.headers.token);
	window.sessionStorage.setItem("uid",res.headers.uid);
	that.$message.success("登陆成功");                       
}).catch((err) => {            
//账号或密码有误时            
var code = err.response.status;	
	return this.$message.error("登录失败!账号或密码错误");
                   
});

main.js
这里从session中获取token,并放到axios的请求头中,后面使用axios的请求都会自动带上token

//vue 的main.js中配置
//给axios请求设置默认的token请求头
const token = window.sessionStorage.getItem("token");
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8';
axios.defaults.headers['Authorization'] = token;
axios.defaults.headers.post['Content-Type'] = 'text/plain';
Vue.prototype.$axios = axios;

记录我的学习笔记

Logo

前往低代码交流专区

更多推荐