Spring Security的搭建(整合JWT)

Spring Security的搭建

一. 认证

1.1 引入依赖

<!--springboot整合security坐标-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>

1.2 创建一个service类 实现UserDetailsService接口

重写loadUserByUsername(String account)方法

@Service
public class SecurityLoginService implements UserDetailsService {

    @Autowired(required = false)
    private PasswordEncoder passwordEncoder;

    @Autowired(required = false)
    private UserDao userDao;

    /**
     * 根据页面传过来的account 去数据库查询该账号信息,放到security提供的user对象中
     * @param account 账号
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException {
        Users users = userDao.queryUserInfoAndAuths(account);
        if (users!=null){
            String auths = String.join(",", users.getAuth());
            return new User(users.getAccount(),passwordEncoder.encode(users.getPassword()),
                    AuthorityUtils.commaSeparatedStringToAuthorityList(auths));
        }else{
            throw new UsernameNotFoundException("用户名和密码错误!");
        }

    }
}

1.3 创建一个config类 继承WebSecurityConfigurerAdapter

重写configure(AuthenticationManagerBuilder auth)方法

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private SecurityLoginService securityLoginService;

    @Bean
    public PasswordEncoder getPassword(){
        return new BCryptPasswordEncoder();
    }

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

二. 认证(自定义登录页面,非前后端分离项目用)

2.1 在SecurityConfig中重写configure(HttpSecurity http)方法

@Override
    protected void configure(HttpSecurity http) throws Exception {
        //告诉security使用自定义登录页面
        http.formLogin()
                .loginPage("/login.html") //指定用户登录页面地址
                .loginProcessingUrl("/doLogin") //表单提交地址,对应表单Action属性
                .permitAll();//除了上面配置的地址,其他请求都会被拦截

        http.authorizeRequests()
                .anyRequest().authenticated();//所有请求都被拦截

        http.csrf().disable();//关闭跨站脚本攻击
    }

三. 配置处理类

3.1 登录成功返回处理类

package com.woniu.handler;

import com.alibaba.fastjson.JSON;
import com.woniu.util.ResponseResult;
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;
import java.io.PrintWriter;

/**
 * 前后端分离的项目情况下,登录成功后返回的不再是一个页面,而是一个json
 * 处理用户登录成功后返回给前端的数据:比如用户名等信息
 */
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        //设置字符集
        httpServletResponse.setContentType("application/json;charset=utf-8");
        PrintWriter pw = httpServletResponse.getWriter();

        String json = JSON.toJSONString(ResponseResult.SECCUSS);

        pw.print(json);
        pw.flush();
        pw.close();

    }
}

3.2 登录失败返回处理类

package com.woniu.handler;

import com.alibaba.fastjson.JSON;
import com.woniu.util.ResponseResult;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

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

/**
 * 登录失败后返回给前端的提示信息
 */
public class LoginFailHandler implements AuthenticationFailureHandler
{
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=utf-8");
        PrintWriter pw = httpServletResponse.getWriter();

        String json = JSON.toJSONString(ResponseResult.FAIL);

        pw.print(json);
        pw.flush();
        pw.close();
    }
}

3.3 拦截没有权限访问该资源的操作

package com.woniu.handler;

import com.alibaba.fastjson.JSON;
import com.woniu.util.ResponseResult;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

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

/**
 * 虽然知道用户名密码
 * 拦截没有权限访问该资源的操作
 */
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=utf-8");
        PrintWriter pw = httpServletResponse.getWriter();

        String json = JSON.toJSONString(ResponseResult.NOAUTH);

        pw.print(json);
        pw.flush();
        pw.close();
    }
}

3.4用户未登录直接访问系统资源

package com.woniu.handler;

import com.alibaba.fastjson.JSON;
import com.woniu.util.ResponseResult;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

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

/**
 * 前后端分离
 * 用户未登录直接访问系统资源
 * 会被该类拦截
 */
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=utf-8");
        PrintWriter pw = httpServletResponse.getWriter();

        String json = JSON.toJSONString(ResponseResult.NOLOGIN);

        pw.print(json);
        pw.flush();
        pw.close();
    }
}

3.5 用户退出处理

package com.woniu.handler;

import com.alibaba.fastjson.JSON;
import com.woniu.util.ResponseResult;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

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

/**
 * 用户退出
 */
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=utf-8");
        PrintWriter pw = httpServletResponse.getWriter();

        String json = JSON.toJSONString(ResponseResult.LOGOUT);

        pw.print(json);
        pw.flush();
        pw.close();
    }
}

四. 整合JWT

4.1 导入JWT依赖和Redis依赖

<!--JWT依赖-->
<dependency>
	<groupId>com.nimbusds</groupId>
	<artifactId>nimbus-jose-jwt</artifactId>
	<version>9.11.1</version>
</dependency>
<!--redis依赖-->
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- 使用 lettuce 时要加这个包;使用 jedis 时则不需要。-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

4.2 导入JWT工具类

package com.woniu.util;

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;

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

/**
 * 生成JWT工具类
 * 1: 创建jwt
 * 2: 校验jwt是否合法
 * 3: 返回载荷部分
 */
public class JWTUtil {

    private static final String KEY="lfasdkjafjlfjdslafjaslfjsaflsjflsjlasjfljlasfjlsfjlsl";

    public static String createJWT(String username) throws Exception {
        /**
         * 第一部分:头部
         */
        //创建JWT的头部
        JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.HS256)
                .type(JOSEObjectType.JWT).build();

        /**
         * 第二部分: 搭载用户信息的地方
         */
        Map map=new HashMap();
        map.put("username",username);
        Payload payload=new Payload(map);

        /**
         * 第三个部分:
         */
        //1.先把头部和载荷加到一起
        JWSObject jwsObject=new JWSObject(jwsHeader,payload);
        //2.创建一个密钥放到JWSSigner中去
        JWSSigner jwsSigner=new MACSigner(KEY);
        //3.根据密钥把jwsObject加密
        jwsObject.sign(jwsSigner);
        //4.把jwt序列化成一个字符串
        String serialize = jwsObject.serialize();
        return serialize;

    }

    /**
     * 拿到参数jwt,根据密钥解码
     */
    public static boolean decode(String jwt) throws Exception {
        JWSVerifier jwsVerifier = new MACVerifier(KEY);
        //把jwt字符串转成一个jwt对象
        JWSObject parse = JWSObject.parse(jwt);
        boolean verify = parse.verify(jwsVerifier);
        return verify;

    }

    /**
     * 获取jwt载荷
     */
    public static Map getPayload(String jwt) throws Exception {
        JWSObject jwsObject = JWSObject.parse(jwt);
        Map map = jwsObject.getPayload().toJSONObject();
        return map;
    }

}

4.3 登录成功处理 返回jwt

package com.woniu.handler;

import com.alibaba.fastjson.JSON;
import com.woniu.util.JWTUtil;
import com.woniu.util.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
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;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;

/**
 * 前后端分离的项目情况下,登录成功后返回的不再是一个页面,而是一个json
 * 处理用户登录成功后返回给前端的数据:比如用户名等信息
 */
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        try {
            //获取登录成功的用户信息
            User user = (User) authentication.getPrincipal();
            String username = user.getUsername();
            String jwt = JWTUtil.createJWT(username);
            //TimeUnit.SECONDS: 秒
            //TimeUnit.MINUTES: 分钟
            redisTemplate.opsForValue().set("jwt:"+user.getUsername(), jwt,1000, TimeUnit.SECONDS);

            //设置字符集
            httpServletResponse.setContentType("application/json;charset=utf-8");
            PrintWriter pw = httpServletResponse.getWriter();

            String json = JSON.toJSONString(new ResponseResult<>().ok(jwt));

            pw.print(json);
            pw.flush();
            pw.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

注:修改config类: successHandler(loginSuccessHandler),并把LoginSuccessHandler载入容器中

4.4 security 整合jwt用的过滤器

/**
 * security 整合jwt用的过滤器
 * 功能:
 * 1: 判断除登录请求外是否携带了jwt, 是: 继续执行下面的逻辑 否: 放掉不处理
 * 2: 判断携带的jwt是否合法, 是: 继续执行下面的逻辑 否: 放掉不处理
 * 3: 拿redis的jwt和请求头的jwt做对比
 *      1): redis的jwt已经过期 放掉不处理
 *      2): 再把jwt的值对比一下 放掉不处理
 */
@Component
public class JWTFilter extends OncePerRequestFilter {

    @Autowired
    private RedisTemplate<String ,String> redisTemplate;

    @Autowired
    private SecurityLoginService securityService;

    @SneakyThrows
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {

        //在请求头拿到jwt
        String jwt = httpServletRequest.getHeader("jwt");
        //1: 判断除登录请求外是否携带了jwt, 否: 放掉不处理
        if (jwt == null){
            //放给security 其他过滤器处理, 该方法不做处理
            filterChain.doFilter(httpServletRequest,httpServletResponse);
            return;
        }

        //2: 判断携带的jwt是否合法, 否: 放掉不处理
        if (!JWTUtil.decode(jwt)){
            filterChain.doFilter(httpServletRequest,httpServletResponse);
            return;
        }

        //3: 拿redis的jwt和请求头的jwt做对比
        //3.1: 获取jwt的用户信息
        Map payload = JWTUtil.getPayload(jwt);
        String username = (String) payload.get("username");
        //3.2: 拿到redis的jwt
        String redisJWT = redisTemplate.opsForValue().get("jwt:" + username);
        //1): redis的jwt已经过期 放掉不处理
        if (redisJWT == null){
            filterChain.doFilter(httpServletRequest,httpServletResponse);
            return;
        }
        //2): 再把jwt的值对比一下 放掉不处理
        if (!jwt.equals(redisJWT)){
            filterChain.doFilter(httpServletRequest,httpServletResponse);
            return;
        }

        //给redis的jwt续期
        redisTemplate.opsForValue().set("jwt:" + username,jwt,2, TimeUnit.MINUTES);

        //获取用户名,密码,权限
        UserDetails userDetails = securityService.loadUserByUsername(username);

        //获取用户信息 生成security容器凭证
        UsernamePasswordAuthenticationToken upa=new UsernamePasswordAuthenticationToken(userDetails.getUsername(),userDetails.getPassword(),userDetails.getAuthorities());
        //往security容器放入凭证
        SecurityContextHolder.getContext().setAuthentication(upa);

        //本方法功能执行完了, 交给下一个过滤器
        filterChain.doFilter(httpServletRequest,httpServletResponse);

    }
}

Spring SecurityJWT整合可以为Web应用提供可靠的安全保障,以下是整合的相关内容: ### 整合方法和步骤 1. **创建项目,编写entity,controller**:搭建Spring Boot项目,编写实体类和控制器类,用于处理业务逻辑和请求响应[^2]。 2. **编写Jwt的过滤器**:包括登录过滤器和验证过滤器。登录过滤器用于处理用户登录请求,验证用户名和密码,若验证成功则生成JWT令牌;验证过滤器用于在每次请求时验证请求中携带的JWT令牌是否有效[^2]。 3. **Spring Security的配置**:对Spring Security进行配置,指定哪些请求需要进行认证和授权,以及如何处理认证和授权过程。同时,将Jwt的过滤器添加到Spring Security的过滤器链中[^2]。 4. **验证效果** - 输入正确的用户名、密码,系统应返回有效的JWT令牌。 - 携带生成的JWT令牌访问受保护的资源,系统应验证令牌的有效性并允许访问。 - 输入错误的账户,系统应返回认证失败的信息。 - 进行权限管理测试,尝试越权访问,系统应拒绝访问并返回相应的错误信息[^2]。 ### 示例代码(部分关键代码) 以下是一个简单的示例代码框架,展示了如何实现JWT过滤器和Spring Security配置: #### Jwt工具类 ```java import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.util.Date; public class JwtUtils { private static final String SECRET_KEY = "yourSecretKey"; private static final long EXPIRATION_TIME = 864_000_000; // 10 days public static String generateToken(String username) { return Jwts.builder() .setSubject(username) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .signWith(SignatureAlgorithm.HS512, SECRET_KEY) .compact(); } public static String getUsernameFromToken(String token) { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody() .getSubject(); } public static boolean validateToken(String token) { try { Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token); return true; } catch (Exception e) { return false; } } } ``` #### Jwt验证过滤器 ```java import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; 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; public class JwtAuthenticationFilter extends OncePerRequestFilter { private final UserDetailsService userDetailsService; public JwtAuthenticationFilter(UserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = getTokenFromRequest(request); if (token != null && JwtUtils.validateToken(token)) { String username = JwtUtils.getUsernameFromToken(token); UserDetails userDetails = userDetailsService.loadUserByUsername(username); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); } filterChain.doFilter(request, response); } private String getTokenFromRequest(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (bearerToken != null && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } } ``` #### Spring Security配置类 ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; 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.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.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { private final UserDetailsService userDetailsService; private final JwtAuthenticationFilter jwtAuthenticationFilter; public SecurityConfig(UserDetailsService userDetailsService, JwtAuthenticationFilter jwtAuthenticationFilter) { this.userDetailsService = userDetailsService; this.jwtAuthenticationFilter = jwtAuthenticationFilter; } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/api/login").permitAll() .anyRequest().authenticated() .and() .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } ```
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值