SpringSecurity 与JsonWebToken的整合

SpringSecurity和Json Web Token的整合应用

SpringSecurity 学习要点:

1.用户在访问资源前,springsecurity会对其进行身份认证和权限认证,在认证都通过的条件下可以正常访问资源.
2.认证过程中,如何处理服务器认证成功和失败.

SpringSecurity快速入门要点

一.自定义登录逻辑

  1. @Configuration注解描述的类是spring的配置类,会在服务器启动时,优先加载,通常用来自定义配置
    @Bean注解将注解通常会在@Configuration注解描述的类中描述方法,用于告诉spring框架这个方法的返回值会交给spring管理(控制反转IOC)
    在这里插入图片描述
  2. 定义UserDetailService接口实现类,自定义登陆逻辑,代码如下:
    UserDetailService为SpringSecurity官方提供的登录逻辑处理对象,我们自己可以实现此接口,然后在对应的方法中进行登录逻辑的编写即可.
@Service //交给spring容器去管理
public class UserDetailServiceImpl implements UserDetailsService {
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;
    /**
     * 当我们执行登录操作时,底层会通过过滤器等对象,调用这个方法
     * @param username 这个参数为页面输出的用户名
     * @return 一般是从数据库基于用户名查询到的用户信息
     * @throws UsernameNotFoundException
     */
    @Autowired
    private UserMapper userMapper;
    @Override
    public UserDetails loadUserByUsername(String username)  {
        //1.基于用户名从数据库查询用户信息
        Map<String, Object> userMap = userMapper.selectUserByUsername(username);
        if (userMap==null) {
            throw new UsernameNotFoundException("user not exists");
        }
        //查询用户权限信息并进行封装
        List<String> userPermissons = userMapper.selectUserPermissions((Long) userMap.get("id"));
        String password = (String)userMap.get("password");
        User user = new User(username,
                password,
                AuthorityUtils
                .createAuthorityList(userPermissons.toArray(new String[]{})));
        System.out.println(user.toString());
        return user;

说明:这里会将查询的用户信息封装传递给SpringSecurity框架

二:修改安全配置

自定义配置类让其实现接口WebSecurityConfigurerAdapter,并重写相关config方法,进行登陆的自定义设计.
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
      return new BCryptPasswordEncoder();
    }
    @Override
    public void configure(HttpSecurity http) throws Exception {
        //1.关闭跨域攻击
        http.csrf().disable();
        //2.配置form认证
         http.formLogin()
                 //登录成功怎么处理(向客户端返回json)
                 .successHandler(successHandler())//成功
                 //登录失败怎么处理?(向客户端返回json)
                 .failureHandler(failureHandler());//失败
        //假如某个资源必须认证才可访问,那没有认证怎么办?
        http.exceptionHandling()
                .authenticationEntryPoint(entryPoint());//提示要认证
//                .accessDeniedHandler(null);//提示访问被拒绝
        //3.所有资源都要认证
        http.authorizeRequests().anyRequest().authenticated();
    }
    //登录成功以后的处理器
    private AuthenticationSuccessHandler successHandler(){
            return (request, response, authentication) -> {
                HashMap<String, Object> map = new HashMap<>();
                    map.put("state",200);
                    map.put("message","login ok");
                //创建一个令牌对象,JWT格式的令牌可以满足这个要求
                HashMap<String, Object> jwtMap = new HashMap<>();
                //获取用户对象,此对象为登录成功以后封装了登录信息的对象
                User principal = (User) authentication.getPrincipal();
                //获取用户权限封装到jwtMap中
                jwtMap.put("username",principal.getUsername());//登录用户
                ArrayList<Object> authoritiesList = new ArrayList<>();
                Collection<GrantedAuthority> as = principal.getAuthorities();
                for(GrantedAuthority a:as){
                    authoritiesList.add(a.getAuthority());
                }
                //上面遍历的新方式
//                user.getAuthorities().forEach((authority)->{
//                        authorities.add(authority.getAuthority());
//
//                });
                jwtMap.put("authorities",authoritiesList);//[sys:res:retrieve,sys:res:create]
                //创建token
                String token = JwtUtils.generatorToken(jwtMap);
                    map.put("token",token);
                    WebUtils.writeJsonToClient(response,map);
            };
    }
    //登录失败的处理器
    private AuthenticationFailureHandler failureHandler(){
            return  ( request,  response,  authException) ->{//lambda 表达式
                HashMap<String, Object> map = new HashMap<>();
                map.put("state",500);
                map.put("message","username or password error");
                try {
                    WebUtils.writeJsonToClient(response,map);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            };
    }
    //假如没有登录访问资源时
    private AuthenticationEntryPoint entryPoint(){
        return ( request, response, authException) ->{
            HashMap<String, Object> map = new HashMap<>();
            map.put("state",500);
            map.put("message","please Login");
            //思考除了返回这些信息,还有返回什么?(JWT令牌)
            WebUtils.writeJsonToClient(response,map);
        };
    }

在UserDetailServiceImpl中与数据库交互的dao类,与配置文件

@Mapper
public interface UserMapper {

    @Select("select * from sys_user where username=#{username}")
    Map<String,Object> selectUserByUsername(@Param("username") String name);

    @Select(" select distinct m.permission " +
            " from sys_user u left join sys_user_role ur on u.id=ur.user_id " +
            " left join sys_role_menu rm on ur.role_id=rm.role_id " +
            " left join sys_menu m on rm.menu_id=m.id " +
            " where u.id=#{id}")
    List<String> selectUserPermissions(@Param("id") Long id);
}
"""application.yml的数据库配置"""
spring:
  datasource:
    url: jdbc:mysql:///databaseName?serverTimezone=Asia/Shanghai&characterEncoding=utf8
    username: username
    password: password
    driver-class-name: com.mysql.cj.jdbc.Driver

Spring认证和授权异常处理

异常类型

对于SpringSecurity框架而言,在实现认证和授权业务时,可能出现如下两大类型异常:
1)AuthenticationException (用户还没有认证就去访问某个需要认证才可访问的方法时,可能出现的异常,这个异常通常对应的状态码401)
2)AccessDeniedException (用户认证以后,在访问一些没有权限的资源时,可能会出现的异常,这个异常通常对应的状态吗为403)

异常处理规范

SpringSecurity框架给了默认的异常处理方式,当默认的异常处理方式不满足我们实际业务需求时,此时我们就要自己定义异常处理逻辑,编写逻辑时需要遵循如下规范:
1)AuthenticationEntryPoint:统一处理 AuthenticationException 异常
2)AccessDeniedHandler:统一处理 AccessDeniedException 异常.

Json Web Token 快速入门要点

两个工具类
一.基于随机盐生成token,包括创建token,解析token,判断token是否失效的类

public class JwtUtils {
    private static String secret="AABBCCDDEEFF";
    /**基于负载和算法创建token信息*/
    public static String generatorToken(Map<String,Object> map){
        return Jwts.builder()
                .setClaims(map)
                .setExpiration(new Date(System.currentTimeMillis()+30*60*1000))
                .setIssuedAt(new Date())
                .signWith(SignatureAlgorithm.HS256,secret)
                .compact();
    }
    /**解析token获取数据*/
    public static Claims getClaimsFromToken(String token){
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
    }
    /**判定token是否失效*/
    public static boolean isTokenExpired(String token){
        Date expiration = getClaimsFromToken(token).getExpiration();
        return expiration.before(new Date());
    }
}

二.向客户端输出Json串的工具类

    public static void writeJsonToClient(HttpServletResponse response,
                                         Map<String,Object> dateMap) throws IOException {
        //1.设置响应数据的编码
        response.setCharacterEncoding("utf-8");
        //2.告诉浏览器响应数据的内容类型以及编码
        response.setContentType("application/json;charset=utf-8");
        //3.将数据转换为json格式
        String jsonStr = new ObjectMapper().writeValueAsString(dateMap);
        //4.获取输出流对象将json数据写到客户端
        PrintWriter out = response.getWriter();
        out.println(jsonStr);
        out.flush();
    }

在资源访问时,配置类,以及授权没有通过的解决方式

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)//加这语句就可以让preAuthorize生效,开启controller安全验证
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //1.关闭跨域攻击
        http.csrf().disable();
        //2.设置拒绝处理器(不允许访问资源时,应该给出什么反馈)
        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler());
        //3.资源访问(所有资源在本项目中的访问不进行认证)
        http.authorizeRequests().anyRequest().permitAll();
    }
    /**资源访问被拒绝的处理器*/
    public AccessDeniedHandler accessDeniedHandler(){
        return (request,response,e)->{
            //1.构建响应信息
            Map<String,Object> map=new HashMap<>();
            map.put("state",403);
            map.put("message","权限不足");
            //2.将响应信息写到客户端
            WebUtils.writeJsonToClient(response,map);
        };
    }
}

定义SpringMVC配置类

在这里插入图片描述
在mvc配置类中添加了一个TokenInterceptor 用来进行对访问的资源,判断请求头的token信息.

public class TokenInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        //1.从请求中获取token对象(如何获取取决于你传递token的方式:header,params)
        String token=request.getHeader("token");
        //2.验证token是否存在
        if(token==null||"".equals(token))
            throw new RuntimeException("请先登录");//WebUtils.writeJsonToClient
        //3.验证token是否过期(将来可以直接去访问认证服务器,在那去校验,谁发的令牌,谁校验令牌)
        if(JwtUtils.isTokenExpired(token))
            throw new RuntimeException("登录超时,请重新登录");
        //4.解析token中的认证和权限信息(一般存储在jwt格式中的负载部分)
        Claims claims=JwtUtils.getClaimsFromToken(token);
        List<String> list=(List<String>)
                claims.get("authorities");//这个名字应该与创建token时,指定的权限名相同
        //5.封装和存储认证和权限信息
        //5.1构建UserDetail对象(用户身份的象征-类似于一张名片,微信的二维码)
        UserDetails userDetails=User.builder()
                .username((String)claims.get("username"))
                .password("")
                .authorities(list.toArray(new String[]{}))
                .build();
        System.out.println(userDetails.toString());
        //5.2构建Security权限交互对象(记住,固定写法)
        PreAuthenticatedAuthenticationToken authToken=
                new PreAuthenticatedAuthenticationToken(
                        userDetails,//用户身份
                        userDetails.getPassword(),
                        userDetails.getAuthorities());
        //5.3将权限交互对象与当前请求进行绑定
        authToken.setDetails(new WebAuthenticationDetails(request));
        //5.4.将认证后的token存储到Security上下文(会话对象)
        SecurityContextHolder.getContext().setAuthentication(authToken);
        return true;
    }
}

controller层展示

在这里插入图片描述
在进行前端页面访问时,update和delete没有该权限,故会报出权限不足.

FAQ 分析

为什么会选择SpringSecurity?(功能强大,SpringBoot诞生后在配置方面做了大量的简化)
SpringSecurity中的加密方式你用的什么?(Bcrypt,底层基于随机盐方式对密码进行hash不可逆加密,更加安全,缺陷是慢)
SpringSecurity中你用过哪些API?(BcryptPasswordEncoder,UserDetailService,UserDetail,User,
AuthenticationSuccessHandler,AuthenticationFailureHandler,…)
为什么要进行权限控制?(防止非法用户破坏数据)
SpringSecurity进行权限控制的步骤(@EnableGlobalMethodSecurity,@PreAuthorize)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

闪耀太阳a

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

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

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

打赏作者

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

抵扣说明:

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

余额充值