Spring Security 初体验

Spring Security 初体验

①认证过滤器(登录)
用于接收前端用户登录信息(username和password)与数据库用户信息(通过UserDetailsService查询)就行判断。
UserDetailsService:查询存在用户信息返回SecurityUser对象,否则抛出异常。
JWTPasswordHandler:判断密码是否正确。

/**
 * @author XS
 * @Version v1.0
 * @ClassName: 认证过滤器
 * @Description:
 * @Date: 2022/2/1 18:44
 */
@Slf4j
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private final AuthenticationManager authenticationManager;

    private RedisTemplate redisTemplate;

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager, RedisTemplate redisTemplate) {
        this.authenticationManager = authenticationManager;
        this.redisTemplate = redisTemplate;
        this.setPostOnly(false);
        //设置登录url和请求方式
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/member/rbac/login", "POST"));
    }

    /**
     * @description: 接收认证信息
     * @author: XS
     * @date: 2022/2/13
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        try {
            //前端用户登录信息
            User user = new ObjectMapper().readValue(request.getInputStream(), User.class);
            return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @description: 登录成功
     * @author: XS
     * @date: 2022/2/10
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        //获取安全框架用户(UserDetailsServiceImpl查到的)
        SecurityUser securityUser = (SecurityUser) authResult.getPrincipal();
        //生成token
        String token = JWTTokenHandler.createToken(securityUser.getCurrentUserInfo().getId(), securityUser.getCurrentUserInfo().getUsername(), securityUser.getPermissionValueList());
        log.info("login success,Token:" + token);
        //返回结果给前端
        HashMap<String, String> login = new HashMap<>();
        login.put("token", token);
        Result<HashMap<String, String>> ok = Result.OK(login);
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().write(new ObjectMapper().writeValueAsString(ok));
        //存入redis
        redisTemplate.opsForValue().set(securityUser.getCurrentUserInfo().getUsername(), token);
    }


    /**
     * @description: 登录失败
     * @author: XS
     * @date: 2022/2/10
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        log.info("login failure,Error:" + failed.toString());
        Result<Object> error = Result.error("登录失败");
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().write(new ObjectMapper().writeValueAsString(error));
    }
}

②授权过滤器(分配角色)
每次请求,判断是否带有token,如果存在则根据token得到用户角色,并在全局安全框架上下文设置用户角色。不存在则直接进入过滤(没角色)。

/**
 * @author XS
 * @Version v1.0
 * @ClassName: 授权过滤器
 * @Description:
 * @Date: 2022/2/1 18:44
 */
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
    public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws IOException, ServletException {
        logger.info("request uri:" + request.getRequestURI());
        String token = request.getHeader(SecurityConstants.TOKEN_HEADER);
        // 如果请求头中没有Authorization信息则直接放行了
        if (StringUtils.isEmpty(token)) {
            logger.warn("X-Token: not exist!");
            SecurityContextHolder.clearContext();
            chain.doFilter(request, response);
            return;
        }
        logger.info("X-Token:" + token);
        // 如果请求头中有token,则进行解析,并且设置授权信息(设置角色)
        //请求头中有 token 并且 token 的格式正确,则进行解析并判断 token 的有效性,然后会在 Spring Security 全局设置授权信息
        SecurityContextHolder.getContext().setAuthentication(getAuthentication(token));
        System.out.println(SecurityContextHolder.getContext());
        super.doFilterInternal(request, response, chain);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(String token) {
        try {
            // 通过 token 获取用户具有的角色
            String username = JWTTokenHandler.getUsernameByToken(token);
            List<String> roles = JWTTokenHandler.getRolesByToken(token);
            //封装List<SimpleGrantedAuthority>
            if (Objects.requireNonNull(roles).size() > 0) {
                List<SimpleGrantedAuthority> authorities = roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
                return new UsernamePasswordAuthenticationToken(username, token, authorities);
            }
        } catch (ExpiredJwtException exception) {
            logger.error("Request to parse JWT with invalid signature . Detail : " + exception.getMessage());
        }
        return null;
    }
}

③实体类
SecurityUser:安全框架实体类
User:前端用户登录信息类

/**
 * @author XS
 * @Version v1.0
 * @ClassName: SecurityUser
 * @Description:
 * @Date: 2022/2/1 16:13
 */
@Data
@Slf4j
public class SecurityUser implements UserDetails {
    //当前登录用户
    private transient User currentUserInfo;

    //当前权限
    private List<String> permissionValueList;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return currentUserInfo.getPassword();
    }

    @Override
    public String getUsername() {
        return currentUserInfo.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}
/**
 * @description: 前端用户登录信息
 * @author: XS
 * @date: 2022/2/17
 */
@Data
@ApiModel(description = "用户实体类")
public class User implements Serializable {
    private String id;
    private String username; // 不要改属性名
    private String password; // 不要改属性名
    private String salt;
    private String token;
}

④处理类
JWTLogoutHandler:登出处理类(登出成功后处理)
JWTPasswordHandler:密码处理类(密码加密和密码比较)
JWTTokenHandler:token处理类(生成token和通过token获取用户信息)

/**
 * @author XS
 * @Version v1.0
 * @ClassName: LogoutHandler
 * @Description:
 * @Date: 2022/2/1 16:07
 */
@Slf4j
public class JWTLogoutHandler implements LogoutSuccessHandler {

    private RedisTemplate redisTemplate;

    public JWTLogoutHandler(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        String token = request.getHeader(SecurityConstants.TOKEN_HEADER);
        if (token != null) {
            //清空当前用户缓存中的权限数据
            String username = JWTTokenHandler.getUsernameByToken(token);
            redisTemplate.delete(username);
        }
        log.info("logout success");
        Result<String> logout = Result.OK("logout success");
        response.setContentType("text/html;charset=utf-8");
        try {
            response.getWriter().write(new ObjectMapper().writeValueAsString(logout));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

/**
 * @author XS
 * @Version v1.0
 * @ClassName: PasswordHandler
 * @Description:
 * @Date: 2022/2/1 15:31
 */
@Slf4j
public class JWTPasswordHandler implements PasswordEncoder {
    @Override
    public String encode(CharSequence originalPassword) {  // 密码加密
        return SecureUtil.md5(originalPassword.toString());
    }

    @Override
    public boolean matches(CharSequence originalPassword, String encodedPassword) {  // 密码加密比较
        return originalPassword.equals(encodedPassword);
        //return SecureUtil.md5(originalPassword.toString()).equals(encodedPassword);  //加密后比较目前没加密
    }
}

/**
 * @author ASUS
 * @Version v1.0
 * @ClassName: JWTTokenHandler
 * @Description:
 * @Date: 2022/2/1 16:00
 */
public class JWTTokenHandler {


    public static String createToken(String id, String username, List<String> roles) {
        String JwtToken = Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                .setSubject("zqy-user")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + SecurityConstants.EXPIRE))
                .claim("id", id)
                .claim("username", username)
                .claim("roles", roles)
                .signWith(SignatureAlgorithm.HS256, SecurityConstants.JWT_SECRET_KEY)
                .compact();

        return JwtToken;
    }

    /**
     * 根据token获取用户Username
     *
     * @param token
     * @return
     */
    public static String getUsernameByToken(String token) {
        if (StringUtils.isEmpty(token)) return "";
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(SecurityConstants.JWT_SECRET_KEY).parseClaimsJws(token);
        Claims claims = claimsJws.getBody();
        return (String) claims.get("username");
    }

    /**
     * 根据token获取用户id
     *
     * @param jwtToken
     * @return
     */
    public static String getUserIdByToken(String jwtToken) {
        if (StringUtils.isEmpty(jwtToken)) return "";
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(SecurityConstants.JWT_SECRET_KEY).parseClaimsJws(jwtToken);
        Claims claims = claimsJws.getBody();
        return (String) claims.get("id");
    }

    /**
     * 根据token获取用户roles
     *
     * @param jwtToken
     * @return
     */
    public static List<String> getRolesByToken(String jwtToken) {
        if (StringUtils.isEmpty(jwtToken)) return null;
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(SecurityConstants.JWT_SECRET_KEY).parseClaimsJws(jwtToken);
        Claims claims = claimsJws.getBody();
        return (List<String>) claims.get("roles");
    }
}

⑤认证详情服务类(数据库查询用户信息)

/**
 * @description: 认证详情(数据库查询)
 * @author: XS
 * @date: 2022/2/17
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Resource
    private SysUserService sysUserService;

    @Resource
    private SysRoleService sysRoleService;

    @Resource
    private UserRoleService userRoleService;

    /***
     * 根据账号获取用户信息
     * @param username:
     * @return: org.springframework.security.core.userdetails.UserDetails
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 从数据库中取出用户信息
        SysUser sysUser = sysUserService.selectByUsername(username);

        // 判断用户是否存在
        if (sysUser == null) {
            throw new UsernameNotFoundException("用户不存在!");
        }
        // 当前用户
        User curUser = new User();
        BeanUtils.copyProperties(sysUser, curUser);
        //查询用户角色
        List<UserRole> userRoleList = userRoleService.getUserRoleByUserId(curUser.getId());
        List<String> roleIdList = userRoleList.stream().map(UserRole::getRoleId).collect(Collectors.toList());
        List<SysRole> sysRoles = sysRoleService.listByIds(roleIdList);
        List<String> authorities = sysRoles.stream().map(sysRole -> "ROLE_" + sysRole.getRoleCode()).collect(Collectors.toList());

        SecurityUser securityUser = new SecurityUser();

        //设置当前用户
        securityUser.setCurrentUserInfo(curUser);
        //设置当前权限(角色)
        securityUser.setPermissionValueList(authorities);
        return securityUser;
    }
}

⑥常量类

/**
 * @author XS
 * @description Spring Security相关配置常量
 */
public final class SecurityConstants {

    /**
     * @description: token过期时间
     * @author: XS
     * @date: 2022/2/13
     */
    public static final long EXPIRE = 1000 * 60 * 60 * 24;  //存在时间

    /**
     * rememberMe 为 false 的时候过期时间是1个小时
     */
    public static final long EXPIRATION = 60 * 60L;

    /**
     * rememberMe 为 true 的时候过期时间是7天
     */
    public static final long EXPIRATION_REMEMBER = 60 * 60 * 24 * 7L;

    /**
     * JWT签名密钥硬编码到应用程序代码中,应该存放在环境变量或.properties文件中。
     */
    public static final String JWT_SECRET_KEY = "C*F-JaNdRgUkXn2r5u8x/A?D(G+KbPeShVmYq3s6v9y$B&E)H@McQfTjWnZr4u7w";

    // 前端token头
    public static final String TOKEN_HEADER = "X-Token";

    // 资源白名单
    public static final String[] RESOURCE_WHITELIST = {
            "/swagger-ui.html",
            "/swagger-ui/*",
            "/swagger-resources/**",
            "/v2/api-docs",
            "/v3/api-docs",
            "/webjars/**",
            //knife4j
            "/doc.html",
    };

    public static final String H2_CONSOLE = "/h2-console/**";

    // 认证白名单(不用登录也能访问的接口)
    public static final String[] AUTHENTICATION_WHITELIST = {
            "/member/rbac/login",
            "/member/rbac/logout",
            "/member/rbac/register",
    };

    //授权白名单(没有权限也能访问的接口)
    public static final String[] AUTHORIZATION_WHITELIST = {
          //TODO
    };

    private SecurityConstants() {
    }

}

⑦核心配置类(将上面自定义的类进行配置)

/**
 * @author XS
 * @Version v1.0
 * @ClassName: Web安全框架配件类
 * @Description:
 * @Date: 2022/2/1 15:28
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Slf4j
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private UserDetailsService userDetailsService;

    @Resource
    private RedisTemplate redisTemplate;


    /**
     * @description: 核心配置
     * @author: XS
     * @date: 2022/2/14
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        log.info("spring-security started successfully!");
        //todo remember me
        http.csrf().disable()//关闭csrf
                .authorizeRequests()
                // 认证白名单(不用认证,不用登录)
                .antMatchers(SecurityConstants.AUTHENTICATION_WHITELIST).anonymous()
                // 授权白名单(无权限请求)
                .antMatchers(SecurityConstants.AUTHORIZATION_WHITELIST).permitAll()
                // 权限请求
                .anyRequest().authenticated().and()
                // 登出路径
                .logout().logoutUrl("/member/rbac/logout")
                // 登出成功处理器
                .logoutSuccessHandler(new JWTLogoutHandler(redisTemplate)).and()
                // 认证过滤器
                .addFilter(new JWTAuthenticationFilter(authenticationManager(), redisTemplate))
                // 授权过滤器
                .addFilter(new JWTAuthorizationFilter(authenticationManager()))
                // 不需要session(不创建会话)
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // 异常处理
                .exceptionHandling()
                // 授权异常
                .authenticationEntryPoint(new JWTAuthenticationEntryPoint())
                // 请求拒绝
                .accessDeniedHandler(new JWTAccessDeniedHandler());
    }

    /**
     * 密码处理
     *
     * @param auth
     * @throws Exception
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(new JWTPasswordHandler());
    }

    /**
     * 配置哪些请求不拦截
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(SecurityConstants.RESOURCE_WHITELIST);
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用和引用,Spring Security是一个用于在Spring应用程序中进行身份验证和授权的框架。它可以通过配置来管理用户认证和授权,提供了一套强大的安全性功能。 在Spring中整合Spring Security,通常需要创建一个Maven项目,并在项目中添加相应的依赖。可以使用注解配置加载Spring容器和安全配置,配置认证页面和授权规则。通过配置WebSecurityConfig类和ApplicationConfig类,指定Spring容器和Spring Security的配置。中的描述,可能是Vue.js中使用的Spring Security自带的登录页面。 在第二版的Spring整合Spring Security中,可能需要导入相关的依赖,如tomcat-embed-jasper等。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Spring整合SpringSecurity(一)](https://blog.csdn.net/qq_45297578/article/details/118998861)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *3* [spring整合springSecurity](https://blog.csdn.net/qq_37023928/article/details/105926488)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值