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);

    }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值