Spring-Security通过Json web token 进行登录认证

        Spring-Security框架用于实现用户登录认证和权限认证,只要用户不登陆或者没有相应的权限就不能访问和请求数据,通过JWT来判断用户是否符合。        

        JWT类似于Session,是一种用于认证,登录的令牌。不同于session存储于服务器内存中,而是保存在用户手中。

一、在登陆成功时创建JWT

        Authentication authentication = new UsernamePasswordAuthenticationToken(
                adminLoginInfoDTO.getUsername(),
                adminLoginInfoDTO.getPassword());
        //开始认证
        Authentication authenticateResult =                     
        authenticationManager.authenticate(authentication);//认证结果

       1.1 登录验证,authenticate()(自动调用UserDetailsServiceImpl中重写的loadUserBy Username 方法)验证所传的的用户名和密码。authentication用来创建验证所需要的的信息。

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    AdminMapper adminMapper;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException{
        log.debug("Spring Security调用了loadUserByUsername()方法,参数:{}", s);
        AdminLoginInfoVo loginInfoByUserName = adminMapper.getLoginInfoByUserName(s);
        log.debug("数据库调用了参数:{}", loginInfoByUserName.getUsername());

        if (loginInfoByUserName == null) {
            log.debug("此用户名【{}】不存在,即将抛出异常");
            String message = "登录失败,用户名不存在!";
            throw new BadCredentialsException(message);
        }

        List<GrantedAuthority> authorities = new ArrayList<>();
        //获取权限放入
        for (String permission : loginInfoByUserName.getPermissions()) {
            GrantedAuthority authority = new SimpleGrantedAuthority(permission);
            authorities.add(authority);
        }

        AdminDetails adminDetails = new AdminDetails(
                loginInfoByUserName.getUsername(),
                loginInfoByUserName.getPassword(),
                loginInfoByUserName.getEnable() == 1,
                authorities);
        adminDetails.setId(loginInfoByUserName.getId());
        return adminDetails;
    }
}

        验证成功后返回adminDetails 对象,对象包含名字、密码、是否启用、权限。

       1.2  用户密码无误后,创建JWT 

        //认证返回结果
        //单独获取结果转换为user类型
        Object principal = authenticateResult.getPrincipal();
        AdminDetails user = (AdminDetails) principal;
        //从登录认证中获取用户名字密码
        Map<String, Object> claims = new HashMap<>();
        claims.put("id", user.getId());//todo id tianjia
        claims.put("username", user.getUsername());
        //过期时间
        Date date = new Date(System.currentTimeMillis() + 10 * 24 * 10 * 60 * 1000);
        String secretKey = "kns439a}fdLK34jsmfd{MF5-8DJSsLKhJNFDSjn";
        //添加到JWYT对象中
        String jwt = Jwts.builder()
                //header 加密方式和类型
                .setHeaderParam("alg", "HS256")
                .setHeaderParam("typ", "JWT")
                //payload 携带参数和过期时间
                .setClaims(claims)
                .setExpiration(date)
                //Signature 加密方式和盐值
                .signWith(SignatureAlgorithm.HS256, secretKey)
                //整合
                .compact();
        return jwt;

二、将认证信息存入SecurityContext中 

        2.1获取用户携带的JWT

        如果用户没有携带JWT放行,过滤器会让用户去访问Security的白名单,去获取JWT。

    public class JwtAuthorizationFilter extends OncePerRequestFilter {
    @Value("${csmall.jwt.secret-key}")
    String secretKey;

    private static final long JWT_MIN_LENGTH = 100;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse             
    response, FilterChain filterChain) throws ServletException, IOException {
        //尝试获取用户的JWT
        String jwt = request.getHeader("Authorization");
        log.debug("接收到jwt数据:{}", jwt);

        //判断是否获取到jwt StringUtils.hasText(jwt)非空,非null
        if (!StringUtils.hasText(jwt) || jwt.length() < JWT_MIN_LENGTH) {
            log.trace("未获取到jwt直接放行");
            filterChain.doFilter(request, response);
            return;
        }

         对获取的JWT解析判断是否通过Jwts.parser().setSigningKey(secretKey) .parseClaimsJws (jwt) . getBody();解析数据是否正确。

//判断验证是否通过
        Claims claims=null;
        try {
            claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();
        } catch (ExpiredJwtException e) {
            String message="JWT过期,请重新登录";
            JSONResult jsonResult =JSONResult.fail(ServiceCode.ERR_JWT_SIGNATURE,message);
            //转化Json数据
            String jsonResultString = JSON.toJSONString(jsonResult);
            PrintWriter printWriter = response.getWriter();
            printWriter.println(jsonResultString);
            printWriter.close();
            return;
        } catch (UnsupportedJwtException e) {
            JSONResult jsonResult =JSONResult.fail(ServiceCode.ERR_BAD_REQUEST,e.getMessage());
            //转化Json数据
            String jsonResultString = JSON.toJSONString(jsonResult);
            PrintWriter printWriter = response.getWriter();
            printWriter.println(jsonResultString);
            printWriter.close();
            return;
        } catch (MalformedJwtException e) {
            String message="非法访问";
            JSONResult jsonResult =JSONResult.fail(ServiceCode.ERR_JWT_MALFORMED,message);
            //转化Json数据
            String jsonResultString = JSON.toJSONString(jsonResult);
            PrintWriter printWriter = response.getWriter();
            printWriter.println(jsonResultString);
            printWriter.close();
            return;
        } catch (SignatureException e) {
            JSONResult jsonResult =JSONResult.fail(ServiceCode.ERR_BAD_REQUEST,e.getMessage());
            //转化Json数据
            String jsonResultString = JSON.toJSONString(jsonResult);
            PrintWriter printWriter = response.getWriter();
            printWriter.println(jsonResultString);
            printWriter.close();
            return;
        } catch (IllegalArgumentException e) {
            String message="非法访问";
            JSONResult jsonResult =JSONResult.fail(ServiceCode.ERR_JWT_SIGNATURE,message);
            //转化Json数据
            String jsonResultString = JSON.toJSONString(jsonResult);
            PrintWriter printWriter = response.getWriter();
            printWriter.println(jsonResultString);
            printWriter.close();
            return;
        }catch (Throwable e){
            e.printStackTrace();
            String message="服务器忙";
            JSONResult jsonResult =JSONResult.fail(ServiceCode.ERR,message);
            //转化Json数据
            String jsonResultString = JSON.toJSONString(jsonResult);
            PrintWriter printWriter = response.getWriter();
            printWriter.println(jsonResultString);
            printWriter.close();
            return;
        }

        2.2将认证信息存入到上下文中        

         判断通过后将数据加入到SecurityCountext中,Security框架会自动判断SecurityCountext中是否存在认证信息,来决定是否放行。


        //解析获取用户数据,setContentType设置文档类型
        log.trace("开始解析用户数据");
        response.setContentType("application/json;charset=utf-8");
        Long id = claims.get("id", Long.class);
        String username = claims.get("username", String.class);
        String authoritiesString = claims.get("authorities", String.class);
        log.trace("id: {}", id);
        log.trace("username: {}", username);
        log.trace("authoritiesString: {}", authoritiesString);

        //通过解析出来的数据创建认证
        //加入权限,将JSON类型转换为SimpleGrantedAuthority类SimpleGrantedAuthority是GrantedAuthority的实现类
        List<SimpleGrantedAuthority> authorities=JSON.parseArray(authoritiesString,
                SimpleGrantedAuthority.class);
        //创建放入SecurityContext中JWT
        LoginPrincipal loginPrincipal=new LoginPrincipal();
        loginPrincipal.setId(id);
        loginPrincipal.setUsername(username);
        Authentication authentication = new UsernamePasswordAuthenticationToken(
                loginPrincipal, null, authorities
        );

        //将认证添加到SecurityContext
        SecurityContext securityContext = SecurityContextHolder.getContext();
        securityContext.setAuthentication(authentication);

        //放行
        filterChain.doFilter(request, response);
    }
}

        将过滤器加入到bean中

        //添加过滤器
        @Autowired
        JwtAuthorizationFilter authorizationFilter;        
        //添加自己的过滤器到密码验证之前
        http.addFilterBefore(authorizationFilter,         
        UsernamePasswordAuthenticationFilter.class);

三、前端登录成功后对JWT的处理

        3.1登录成功后会将jwt存放到 localStorage中

      console.log('登录成功,服务器响应的JWT=' + jwt);
      localStorage.setItem('jwt', jwt);

        3.2其他的页面也需要将jwt存储在请求头中

        在对后端发送请求时,会携带jwt,过滤器再次验证。

 this.axios
          .create({'headers': {'Authorization': localStorage.getItem('jwt')}})
          .post(url).then((response) => {})

四、Security框架加密处理

        Security配置类,对密码加密和解密

    @Bean
    public PasswordEncoder passwordEncoder() {
//密码判断,他返回的这个就是密码加密的格式,所以他自动调用authenticate方法就会知道你的加密方式
        log.trace("SecurityConfiguration 正在加载");
        return new BCryptPasswordEncoder();
        //return NoOpPasswordEncoder.getInstance(); // 无操作的密码编码器,即:不会执行加密处理
    }

    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
//判断注入,框架自带的,只需要注入就可以掉用authenticate自动判断密码用户是否正确
        return super.authenticationManager();
    }

        在用户注册时调用此方法,对密码加密 

String encode = passwordEncoder.encode(rowPassword);
        a.setPassword(encode);

        在用户登陆时调用authenticationManager.authenticate对密码解析判断

 Authentication authentication = new UsernamePasswordAuthenticationToken(
                adminLoginInfoDTO.getUsername(),
                adminLoginInfoDTO.getPassword());
        //开始认证
        Authentication authenticateResult = authenticationManager.authenticate(authentication);

         authenticate是自动调用一下类的方法做判断。

@Slf4j
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    AdminMapper adminMapper;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        log.debug("Spring Security调用了loadUserByUsername()方法,参数:{}", s);
        AdminLoginInfoVo loginInfoByUserName = adminMapper.getLoginInfoByUserName(s);
        log.debug("数据库调用了参数:{}", loginInfoByUserName.getUsername());

        if (loginInfoByUserName == null) {
            log.debug("此用户名【{}】不存在,即将抛出异常");
            String message = "登录失败,用户名不存在!";
            throw new BadCredentialsException(message);
        }

        List<GrantedAuthority> authorities = new ArrayList<>();
        //获取权限放入
        for (String permission : loginInfoByUserName.getPermissions()) {
            GrantedAuthority authority = new SimpleGrantedAuthority(permission);
            authorities.add(authority);
        }

        AdminDetails adminDetails = new AdminDetails(
                loginInfoByUserName.getUsername(),
                loginInfoByUserName.getPassword(),
                loginInfoByUserName.getEnable() == 1,
                authorities);
        adminDetails.setId(loginInfoByUserName.getId());
        return adminDetails;

    }
}

四、Security框架对权限的判断和调用

        如何获取springsecurity上下文中信息

//通过过滤器获取从前端用户登录的信息
    public CsmallAuthenticationInfo getUserInfo() {
        UsernamePasswordAuthenticationToken authenticationToken =
                (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext()
                        .getAuthentication();
}

@EnableGlobalMethodSecurity(prePostEnabled = true)在配置类上添加启动

@PreAuthorize("hasAuthority('/ams/admin/read')")在controller上添加,开启方法权限判断

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以通过在Spring Security中配置一个Token认证过滤器来实现基于WebSocket的Token认证。具体步骤如下: 1. 创建一个TokenAuthenticationFilter类,继承自OncePerRequestFilter并实现doFilterInternal方法。该类负责检查请求中是否包含有效的Token,并进行相应的认证处理。 ```java public class TokenAuthenticationFilter extends OncePerRequestFilter { private final TokenProvider tokenProvider; public TokenAuthenticationFilter(TokenProvider tokenProvider) { this.tokenProvider = tokenProvider; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = getTokenFromRequest(request); if (StringUtils.hasText(token) && tokenProvider.validateToken(token)) { Authentication authentication = tokenProvider.getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(authentication); } filterChain.doFilter(request, response); } private String getTokenFromRequest(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } } ``` 2. 创建一个TokenProvider类,用于生成Token和验证Token的有效性,并根据Token获取用户信息。 ```java @Component public class TokenProvider { private static final String SECRET_KEY = "my-secret-key"; private static final long EXPIRATION_TIME = 86400000; // 1 day public String generateToken(Authentication authentication) { UserPrincipal principal = (UserPrincipal) authentication.getPrincipal(); Date expirationDate = new Date(System.currentTimeMillis() + EXPIRATION_TIME); return Jwts.builder() .setSubject(Long.toString(principal.getId())) .setIssuedAt(new Date()) .setExpiration(expirationDate) .signWith(SignatureAlgorithm.HS512, SECRET_KEY) .compact(); } public boolean validateToken(String token) { try { Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token); return true; } catch (Exception e) { return false; } } public Authentication getAuthentication(String token) { Claims claims = Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody(); Long userId = Long.parseLong(claims.getSubject()); UserPrincipal principal = new UserPrincipal(userId); return new UsernamePasswordAuthenticationToken(principal, "", principal.getAuthorities()); } } ``` 3. 在配置类中注册TokenAuthenticationFilter和TokenProvider,并将TokenAuthenticationFilter添加到Spring Security的过滤器链中。 ```java @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Autowired private TokenProvider tokenProvider; @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic"); config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws").setAllowedOriginPatterns("*").withSockJS(); } @Override public void configureClientInboundChannel(ChannelRegistration registration) { registration.interceptors(new ChannelInterceptorAdapter() { @Override public Message<?> preSend(Message<?> message, MessageChannel channel) { StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); if (StompCommand.CONNECT.equals(accessor.getCommand())) { String token = accessor.getFirstNativeHeader("Authorization"); if (StringUtils.hasText(token) && token.startsWith("Bearer ")) { token = token.substring(7); TokenAuthenticationFilter filter = new TokenAuthenticationFilter(tokenProvider); SecurityContextHolder.getContext().setAuthentication(filter.getAuthentication(token)); } } return message; } }); } @Override public void configureClientOutboundChannel(ChannelRegistration registration) { } @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { } @Override public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) { } @Override public boolean configureMessageConverters(List<MessageConverter> messageConverters) { return true; } @Override public void configureWebSocketTransport(WebSocketTransportRegistration registry) { } @Bean public TokenAuthenticationFilter tokenAuthenticationFilter() throws Exception { TokenAuthenticationFilter filter = new TokenAuthenticationFilter(tokenProvider); filter.setAuthenticationManager(authenticationManager()); return filter; } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable().authorizeRequests() .antMatchers("/api/auth/**").permitAll() .anyRequest().authenticated(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService()) .passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override @Bean public UserDetailsService userDetailsService() { return new UserDetailsServiceImpl(); } } ``` 在上述代码中,我们通过重写configureClientInboundChannel方法,在连接到WebSocket时获取请求中的Token,并使用TokenAuthenticationFilter进行认证。注意,我们需要将TokenAuthenticationFilter添加到Spring Security的过滤器链中,以便它能够在WebSocket连接期间对请求进行拦截。 最后,我们需要在客户端的连接请求中添加Authorization头部,以便在服务端进行Token认证。例如: ```javascript stompClient.connect({}, function(frame) { console.log('Connected: ' + frame); stompClient.subscribe('/topic/greetings', function(greeting) { showGreeting(JSON.parse(greeting.body).content); }); }, function(error) { console.log('Error: ' + error); }, {"Authorization": "Bearer " + token}); ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值