springboot+spring security+JWT实现方式

本教程值值详细讲解spring security的实现,没有任何杂质,只是用于记录。不公开!!

SpringBoot项目的创建就不在说了,直接跳过

一、引入相关依赖

<!-- Spring Security -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

二、写了几个工具类

  1. 定义后台统一返回代码
/**
 ******************************************************************
 * 后台统一返回代码
 ******************************************************************
 * 作者:
 * 时间:
 */
public enum ResultEnum {

    SUCCESS(200, "请求成功"),
    FAILURE(100, "请求失败"),
    USER_NO_ACCESS(300, "用户无权访问"),
    USER_NEED_AUTHORITIES(102, "用户未登录"),
    USER_LOGIN_FAILED(101,"用户账号或密码错误"),
    USER_LOGIN_SUCCESS(201, "用户登录成功"),
    USER_LOGOUT_SUCCESS(202, "用户登出成功"),
    TOKEN_IS_BLACKLIST(301, "token失效"),
    LOGIN_IS_OVERDUE(207, "登录已失效");

    private Integer code;

    private String message;

    ResultEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

    /**
     * 根据枚举code,返回枚举项
     * @param code  枚举代码
     * @return  返回值:枚举项
     */
    public static ResultEnum parse(int code){
        ResultEnum[] values = values();
        for (ResultEnum value : values) {
            if(value.getCode() == code){
                return value;
            }
        }
        throw  new RuntimeException("未知代码");
    }
}
  1. 定义后台同意返回数据模板
/**
 ******************************************************************
 * 定义后台返回对象模板
 ******************************************************************
 * 作者:
 * 时间:
 */
public class AjaxDone {
    private Integer code;       // 请求状态码
    private String msg;         // 请求提时信息
    private Object data;        // 请求数据
	
	// 省略getter和getter方法
}

三、实现Spring Security各个核心接口,处理不同状态

  1. 实现AuthenticationEntryPoint接口,处理用户未登录的状况
/**
 ******************************************************************
 * 用户未登陆时,访问接口时返回的数据
 ******************************************************************
 * 作者:
 * 时间:
 */
@Component
public class AjaxAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json;charset:utf-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(new AjaxDone(ResultEnum.USER_NEED_AUTHORITIES)));
    }
}
  1. 实现AccessDeniedHandler接口,处理无权访问的情况
/**
 ******************************************************************
 * 用户无权访问接口时返回的数据
 ******************************************************************
 * 作者:
 * 时间:
 */
@Component
public class AjaxAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json;charset:utf-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(new AjaxDone(ResultEnum.USER_NO_ACCESS)));
    }
}
  1. 实现AuthenticationFailureHandler接口,处理用户登录失败
/**
 ******************************************************************
 * 用户访问登录接口,登陆失败的时候,返回的 数据
 ******************************************************************
 * 作者:
 * 时间:
 */
@Component
public class AjaxAuthenticationFailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json;charset:utf-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(new AjaxDone(ResultEnum.USER_LOGIN_FAILED)));
    }

}
  1. 实现AuthenticationSuccessHandler接口,处理登录成功的情况
/**
 ******************************************************************
 * 用户访问登录接口,登陆成功的时候,返回的 数据
 ******************************************************************
 * 作者:
 * 时间:
 */
@Component
public class AjaxAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        SelfUserDetails userDetails = (SelfUserDetails) authentication.getPrincipal();

        // 生成token,以后完成
        String jwtToken = "token";
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json;charset:utf-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(new AjaxDone(ResultEnum.USER_LOGIN_SUCCESS, jwtToken)));
    }
}
  1. 实现LogoutSuccessHandler接口,处理退出成功
/**
 ******************************************************************
 * 用户访问登出接口,登出成功的时候,返回的数据
 ******************************************************************
 * 作者:
 * 时间:
 */
@Component
public class AjaxLogoutSuccessHandler implements LogoutSuccessHandler {

    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json;charset:utf-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(new AjaxDone(ResultEnum.USER_LOGOUT_SUCCESS)));
    }
}
  1. 实现UserDetails实现自定义对象
/**
 ******************************************************************
 * 实现系统的自定义对象
 ******************************************************************
 * 作者:
 * 时间:
 */
public class SelfUserDetails implements UserDetails {
 
    private Integer id;
    private String username;
    private String password;
    private Set<? extends GrantedAuthority> authorities;
 
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() { return this.authorities; }
 
    public void setAuthorities(Set<? extends GrantedAuthority> authorities) { this.authorities = authorities; }
 
 	// 最重点Ⅰ
    @Override
    public String getPassword() { return this.password; }
 
 	// 最重点Ⅱ
    @Override
    public String getUsername() { return this.username; }
 
    public void setUsername(String username) { this.username = username; }
 
    public void setPassword(String password) { this.password = password; }
 
    //账号是否过期
    @Override
    public boolean isAccountNonExpired() { return true; }
 
    //账号是否锁定
    @Override
    public boolean isAccountNonLocked() { return true; }
 
    //账号凭证是否未过期
    @Override
    public boolean isCredentialsNonExpired() { return true; }
 
    @Override
    public boolean isEnabled() { return true; }
 
    public Integer getId() { return id; }
 
    public void setId(Integer id) { this.id = id; }
}

四、权限访问控制

  1. 实现自定义公式验证验证权限
/**
 ******************************************************************
 * 自定义公式,权限验证
 ******************************************************************
 * 作者:
 * 时间:
 */
@Component("rbacauthorityservice")
public class RbacAuthorityService {
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
 
        Object userInfo = authentication.getPrincipal();
 
        boolean hasPermission  = false;
 
        if (userInfo instanceof UserDetails) {
 
            String username = ((UserDetails) userInfo).getUsername();
 
 			// 这可以在redis中获取
            // 获取资源
            Set<String> urls = new HashSet();
            // 这些 url 都是要登录后才能访问,且其他的 url 都不能访问!
            urls.add("/demo/**");//application.yml里设置了项目路径,百度一下我就不贴了
 
            AntPathMatcher antPathMatcher = new AntPathMatcher();
 
            for (String url : urls) {
                if (antPathMatcher.match(url, request.getRequestURI())) {
                    hasPermission = true;
                    break;
                }
            }
            return hasPermission;
        } else {
            return false;
        }
    }
}
  1. 实现自定义的jwt拦截器
/**
 ******************************************************************
 * 每次请求,都会经过这个过滤器,将用户解析出来放到上下文中
 * 模拟已经登陆的状态
 ******************************************************************
 * 作者:
 * 时间:
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    SelfUserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String authHeader = request.getHeader("Authorization");

        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            String authToken = authHeader.substring("Bearer ".length());

            // String username = JwtTokenUtil.parseToken(authToken, "_secret");
            // 这里先不解析,写死为admin
            String username = "admin";

            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {

                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                if (userDetails != null) {
                    UsernamePasswordAuthenticationToken authentication =
                            new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }
        filterChain.doFilter(request, response);
    }

}

五、Spring Security核心配置

  1. 继承UserDetailsService,用户认证的业务代码
/**
 ******************************************************************
 * 用户认证的业务逻辑
 ******************************************************************
 * 作者:
 * 时间:
 */
@Component
public class SelfUserDetailsService implements UserDetailsService {

    private Logger logger = LoggerFactory.getLogger(SelfUserDetailsService.class);

    @Autowired
    private UserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        //通过username查询用户
        // 这里先固定死用户, 真正的业务需要从数据库获取
        SelfUserDetails user = userService.findUser(username);
        if(user == null){
            //仍需要细化处理
            throw new UsernameNotFoundException("该用户不存在");
        }

        Set authoritiesSet = new HashSet();
        // 模拟从数据库中获取用户角色
        GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_ADMIN");

        authoritiesSet.add(authority);
        user.setAuthorities(authoritiesSet);

        logger.info("用户{}验证通过",username);
        return user;
    }
}
  1. 核心处理类
/**
 ******************************************************************
 * Spring Security核心配置类
 ******************************************************************
 * 作者:
 * 时间:
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SpringSecurityConf extends WebSecurityConfigurerAdapter {
    @Autowired
    AjaxAuthenticationEntryPoint authenticationEntryPoint;//未登陆时返回 JSON 格式的数据给前端(否则为 html)

    @Autowired
    AjaxAuthenticationSuccessHandler authenticationSuccessHandler; //登录成功返回的 JSON 格式数据给前端(否则为 html)

    @Autowired
    AjaxAuthenticationFailureHandler authenticationFailureHandler; //登录失败返回的 JSON 格式数据给前端(否则为 html)

    @Autowired
    AjaxLogoutSuccessHandler logoutSuccessHandler;//注销成功返回的 JSON 格式数据给前端(否则为 登录时的 html)

    @Autowired
    AjaxAccessDeniedHandler accessDeniedHandler;//无权访问返回的 JSON 格式数据给前端(否则为 403 html 页面)

    @Autowired
    SelfUserDetailsService userDetailsService; // 自定义user

    @Autowired
    JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; // JWT 拦截器

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 加入自定义的安全认证
//        auth.authenticationProvider(provider);
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // 去掉 CSRF
        http.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 使用 JWT,关闭token
                .and()
                .httpBasic().authenticationEntryPoint(authenticationEntryPoint)

                .and()
                .authorizeRequests()//定义哪些URL需要被保护、哪些不需要被保护
                // 定义禁止拦截的资源
                .antMatchers("/test/yu").permitAll()

                // 定义指定的拦截
               .anyRequest()//任何请求,登录后可以访问
                .access("@rbacauthorityservice.hasPermission(request,authentication)") // RBAC 动态 url 认证

                .and()
                .formLogin()  //开启登录, 定义当需要用户登录时候,转到的登录页面
//                .loginPage("/test/login.html")
//                .loginProcessingUrl("/login")
                .successHandler(authenticationSuccessHandler) // 登录成功
                .failureHandler(authenticationFailureHandler) // 登录失败
                .permitAll()

                .and()
                .logout()//默认注销行为为logout
                .logoutUrl("/logout")
                .logoutSuccessHandler(logoutSuccessHandler)
                .permitAll();

        // 记住我
        http.rememberMe().rememberMeParameter("remember-me")
                .userDetailsService(userDetailsService).tokenValiditySeconds(1000);

        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler); // 无权访问 JSON 格式的数据
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); // JWT Filter
    }
}

这里密码使用BCryptPasswordEncoder加密,官方也推荐这个,将密码保存到数据库,记得用这个类加密一下哦。

public static void main(String[] args) {
    BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
    String password = encoder.encode("000000");           // 将字符串‘000000’加密
    
    boolean b = encoder.matches("000000", password);      // 对比密码
}

来源:https://blog.csdn.net/zzxzzxhao/article/details/83381876

参考:
https://blog.csdn.net/zimou5581/article/details/89511381
https://blog.csdn.net/sinat_29899265/article/details/80771330
https://www.jianshu.com/p/ca4cebefd1cc

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值