SpringBoot 集成 SpringSecurity+JWT

SpringSecurity,JWT简介

1.什么是SpringSecurity

SpringSecurity是一个功能强大且高度可定制的身份验证和访问控制框架,提供了完善的认证机制和方法级的授权功能,是一款非常优秀的权限管理框架,他的核心是一组过滤链,不同的功能经由不同的过滤器

2.什么是JWT

Json Web Token(JWT) 是为了在网络应用环境间传递声明而执行的一种基于json的开放标准(RFC 7519),token设计为紧凑且安全,特别适用于分布式站点的单点登录(SSO)场景

jwt实际是一个字符串,由头部,载荷,签名组成,用[.]分隔,在服务器直接根据token取出保存的用户信息,即可对token的可用性进行校验,使得单点登录更简单

SpringBoot 集成 SpringSecurity+JWT步骤

1.项目结构

 2.数据库和实体类以及准备【注意:】为了方便只用了两张表,实体类省略

users表:

DROP TABLE IF EXISTS `users`;
CREATE TABLE `users`  (
  `user_id` int NOT NULL AUTO_INCREMENT,
  `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
  `user_pwd` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
  PRIMARY KEY (`user_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;

role表:

DROP TABLE IF EXISTS `role`;
CREATE TABLE `role`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_id` int NULL DEFAULT NULL,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;

3.创建UserDetailsServiceImpl,编写登录操作

package cn.zb.security.service.impl;

import cn.zb.security.entity.Role;
import cn.zb.security.entity.Users;
import cn.zb.security.service.RoleService;
import cn.zb.security.service.UsersService;
import org.springframework.beans.factory.annotation.Autowired;
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 java.util.ArrayList;
import java.util.List;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private UsersService usersService;

    @Autowired
    private RoleService roleService;

    /**
     * 用户登录
     * @param s 获取到的UserName,
     * @return user 返回的是org.springframework.security.core.userdetails.User;
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        if (s==null || "".equals("s")){
            throw new RuntimeException("用户不能为空");
        }
        //根据用户名查询对象
        Users user = usersService.findUserByUserName(s);
        if (user==null){
            throw new RuntimeException("用户不存在");
        }
        List<SimpleGrantedAuthority> authorities=new ArrayList<>();
        //根据userId获取Role对象
        List<Role> roles = roleService.findRoleByUserId(user.getUserId());
        for (Role role:roles) {
            authorities.add(new SimpleGrantedAuthority("ROLE_"+role.getName()));
        }
        //这里没有对用户密码进行判断,将数据库中查到的密码封装到了对象中,在WebSecurityConfig中configure()方法中,User进行了验证,
        //前端传递的密码是明文,数据中存的是暗文,需要对前端传递的密码加密,进行验证,加密方法是new BCryptPasswordEncoder().encode("pwd")
        return new User(user.getUserName(),user.getUserPwd(),authorities);
    }
}

4.创建JwtUtils工具类,做token的生成和校验

package cn.zb.security.util;

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

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

public class JwtUtils {
    public static final String TOKEN_HEADER="Authorization"; //token请求头
    public static final String TOKEN_PREFIX="Bearer";//token前缀
    public static final long EXPIRATION=60*60*1000; //token有效期
    public static final String SUBJECT="piconjo"; //签名主题
    public static final String HEADER_STRING="Passport"; //配置token在http heads中的键值
    public static final String APPSECRET_KEY="piconjo_secret"; //应用密钥
    public static final String ROLE_CLAIMS="role"; //角色权限声明

    //生成token
    public static String createToken(String username,String role){
        HashMap<String, Object> map = new HashMap<>();
        map.put(ROLE_CLAIMS,role);
        String token=Jwts.builder()
                        .setSubject(username)
                        .setClaims(map)
                        .claim("username",username)
                        .setIssuedAt(new Date())
                        .setExpiration(new Date(System.currentTimeMillis()+EXPIRATION))
                        .signWith(SignatureAlgorithm.HS512,APPSECRET_KEY)
                        .compact();
        return token;
    }

    //校验token
    public static Claims checkJWT(String token){
       try{
           Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
           return claims;
       }catch (Exception e){
           e.printStackTrace();
           return null;
       }
    }

    //从Token中获取username
    public static String getUsername(String token){
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        return claims.get("username").toString();
    }

    //从Token中获取用户角色
    public static String getUserRole(String token){
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        return claims.get("role").toString();
    }

    //校验Token是否过期
    public static boolean isExpiration(String token){
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        return claims.getExpiration().before(new Date());
    }
}

5.创建拦截器JWTAuthenticationFilter

springsercurity对UserDetailsServiceImpl返回的user进行验证,验证成功则生成token,失败则返回失败信息

package cn.zb.security.util;
import com.alibaba.fastjson.JSON;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

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.Collection;


public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    private AuthenticationManager authenticationManager;

    public JWTAuthenticationFilter (AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
        //默认的登录路径是/login,post请求
        super.setFilterProcessesUrl("/api/login");
    }

    //验证操作, 接受并解析用户凭证
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        //从输入流中获取到登录的信息
        //创建一个token并条用authenticationManager.authenticate 让Spring Security进行验证
        return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
                request.getParameter("username"),request.getParameter("password")));
    }

    /**
     * 验证【成功】后调用的方法
     * 若验证成功 生成token并返回
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException {
        User user= (User) authResult.getPrincipal();
        System.out.println(user.toString());
        // 从User中获取权限信息
        Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
        // 创建Token
        String token = JwtUtils.createToken(user.getUsername(), authorities.toString());

        // 设置编码 防止乱码问题
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        // 在请求头里返回创建成功的token
        // 设置请求头为带有"Bearer "前缀的token字符串
        response.setHeader("token", JwtUtils.TOKEN_PREFIX + token);
        // 处理编码方式 防止中文乱码
        response.setContentType("text/json;charset=utf-8");
        // 将反馈塞到HttpServletResponse中返回给前台
        response.getWriter().write(JSON.toJSONString("登录成功"));
    }
    /**
     * 验证【失败】调用的方法
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        String returnData="";
        // 账号过期
        if (failed instanceof AccountExpiredException) {
            returnData="账号过期";
        }
        // 密码错误
        else if (failed instanceof BadCredentialsException) {
            returnData="密码错误";
        }
        // 密码过期
        else if (failed instanceof CredentialsExpiredException) {
            returnData="密码过期";
        }
        // 账号不可用
        else if (failed instanceof DisabledException) {
            returnData="账号不可用";
        }
        //账号锁定
        else if (failed instanceof LockedException) {
            returnData="账号锁定";
        }
        // 用户不存在
        else if (failed instanceof InternalAuthenticationServiceException) {
            returnData="用户不存在";
        }
        // 其他错误
        else{
            returnData="未知异常";
        }

        // 处理编码方式 防止中文乱码
        response.setContentType("text/json;charset=utf-8");
        // 将反馈塞到HttpServletResponse中返回给前台
        response.getWriter().write(JSON.toJSONString(returnData));
    }
}

6.创建拦截器JWTAuthorizationFilter

对前端传递的请求拦截,取出里面的token做token校验,获取UsernamePasswordAuthenticationToken对象返回springsecurity,进行相关的角色判断

package cn.zb.security.util;

import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint;
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.ArrayList;
import java.util.List;

public class JWTAuthorizationFilter extends BasicAuthenticationFilter {

    private AuthenticationManager authenticationManager;

    public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

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

        String tokenHeader=request.getHeader(JwtUtils.TOKEN_HEADER);
        //因为设置拦截全部请求,所有这里没有token放行之后是会返回没有登录
        if (tokenHeader==null || !tokenHeader.startsWith(JwtUtils.TOKEN_PREFIX)){
            chain.doFilter(request,response);
            return;
        }
        //若请求头中由token,则调用下面的方法进行解析,并设置认证信息
        SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
        super.doFilterInternal(request,response,chain);
    }

    public UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader){
        //去掉前缀,获取token字符串
        String token = tokenHeader.replace(JwtUtils.TOKEN_PREFIX, "");
        //从token中解密获取用户用户名
        String username=JwtUtils.getUsername(token);
        //从token中解密获取用户角色
        String role=JwtUtils.getUserRole(token);
        String[] roles = StringUtils.strip(role, "[]").split(",");
        List<SimpleGrantedAuthority> authorities=new ArrayList<>();
        for (String s : roles) {
            authorities.add(new SimpleGrantedAuthority(s));
        }
        if (username!=null){
            //这里像SpringSecurity声明用户角色,做相关操作放行
            return new UsernamePasswordAuthenticationToken(username,null,authorities);
        }
        return null;
    }
}

7.创建WebSecurityConfig配置类,对springsecurity进行相关配置

package cn.zb.security.config;


import cn.zb.security.util.JWTAuthenticationEntryPoint;
import cn.zb.security.util.JWTAuthenticationFilter;
import cn.zb.security.util.JWTAuthorizationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    @Qualifier("userDetailsServiceImpl")
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    /**
     * 安全配置
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors()
                .and()
                //跨域伪造请求限制无效
                .csrf().disable()
                .authorizeRequests()
                //访问/data路径下的请求需要admin
                .antMatchers("/data/**").hasRole("admin")
                //拦截所有请求
                .antMatchers("/").authenticated()
                //对登录做放行,生成token
                .antMatchers("/api/login").permitAll()
                .and()
                //添加jwt登录拦截器
                .addFilter(new JWTAuthenticationFilter(authenticationManager()))
                //添加jwt鉴权拦截器
                .addFilter(new JWTAuthorizationFilter(authenticationManager()))
                .sessionManagement()
                //关闭session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                //异常处理
                .exceptionHandling()
                .authenticationEntryPoint(new JWTAuthenticationEntryPoint());

    }

    //使用BCryptPasswordEncoder密码加密
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    //跨域配置
    @Bean
    CorsConfigurationSource corsConfigurationSource(){
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        //注册跨域配置
        source.registerCorsConfiguration("/**",new CorsConfiguration().applyPermitDefaultValues());
        return source;
    }

    //静态资源放行
    @Override
    public void configure(WebSecurity web) {
        // 设置拦截忽略文件夹,可以对静态资源放行
        //web.ignoring().antMatchers("/images/**");
    }
}

8.创建Controller类,进行代码测试

我们在WebSecurityConfig 配置过,想要访问/data下的方法需要用户拥有admin角色

package cn.zb.security.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/data")
public class UserController {
    @GetMapping("/test")
    private String data() {
        return "访问成功";
    }
}

9.操作截图

用admin和as两个用户,其中as是没有admin权限的

 

 本文做记录使用,参考了其他博主

  • 5
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值