spring security详解

框架的作用:

认证(确认用户的身份)

授权(用户有哪些权限,可以调哪些接口)

一、工作原理

https://blog.csdn.net/wu2374633583/article/details/108199205

https://www.cnblogs.com/dalianpai/p/12364330.html

https://www.cnblogs.com/wangstudyblog/p/14793305.html

可以看这三篇讲过滤器的讲的比较详细

FilterChain doFilter方法 进入到过滤器链,多个过滤器封装在FilterChainProxy 中,FilterChainProxy doFilter方法
SecurityContextPersistenceFilter 保存到SecurityContext(安全上下文)中

二、来看一下认证流程

AuthenticationManager(认证管理器)

authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(username, password))-->该方法会去调用UserDetailsServiceImpl.loadUserByUsername 

自定义逻辑控制  只需要重写UserDetailsService.loadUserByUsername方法  (查询当前用户名是否在数据库中)

详细的见 https://blog.csdn.net/wwang_dev/article/details/119107355

三、如何自定义配置

之前的过滤器链的博客就有提到(SpringSecurity(八):过滤器链 - 刚刚好。 - 博客园 (cnblogs.com)

WebSecurityConfigurer  ---用户扩展用户自定义的配置

你可以通过这个类 配置白名单  配置自定义过滤器  配置异常处理器

四、实现  spring security+jwt

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    JwtLogoutSuccessHandler jwtLogoutSuccessHandler;

    @Autowired
    JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    @Autowired
    UserDetailServiceImpl userDetailService;

    @Bean
    JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
        JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager());
        return jwtAuthenticationFilter;
    }

    /**
     * 解决 自定义login方法需要注入AuthenticationManager,但是没有,所以在这里注入 AuthenticationManager
     *
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception
    {
        return super.authenticationManagerBean();
    }


    /**
     * AuthenticationEntryPoint 用来解决匿名用户访问无权限资源时的异常
     * AccessDeineHandler 用来解决认证过的用户访问无权限资源时的异常
     * accessDeniedHandler 抛出的异常会被GloableExceptionHandler捕获,因此直接在全局异常处理器捕获把,不在这里写了,spring sccurity官方也不做处理
     * 参考:https://github.com/spring-projects/spring-security/issues/6908
     *
     *
     **/
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                .logout().logoutSuccessHandler(jwtLogoutSuccessHandler)
                // 禁用session
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

                // 配置白名单
                .and().authorizeRequests()
                .antMatchers(HttpMethod.GET,
                        "/",
                        "/*.html",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js"
                ).permitAll()
                .antMatchers("/api/email/**","/api/sms/**").permitAll()
                .antMatchers("/login","/logout").anonymous()
                //swagger
                .antMatchers( "/doc.html","/swagger-resources/**","/webjars/**","/*/api-docs","/favicon.ico").anonymous()
//                .anyRequest().permitAll()
                .anyRequest().authenticated()
                // 异常处理器
                .and().exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
                // 配置自定义的过滤器
                .and()
                .addFilter(jwtAuthenticationFilter())


        ;

    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailService);
    }
    /**
     * 强散列哈希加密实现
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder()
    {
        return new BCryptPasswordEncoder();
    }


}
//异常处理器
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { @Autowired JwtUtils jwtUtils; @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_OK); ServletOutputStream outputStream = response.getOutputStream(); ResultBean result = null; if (request.getHeader(jwtUtils.getHeader())==null){ result = ResultBean.fail(RetCode.UNAUTHEN.code,"令牌不存在,请重新登录"); } else { result = ResultBean.fail(RetCode.UNAUTHEN.code,"认证失败"); } System.out.println("请求错误,进入AuthenticationEntryPoint"); outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8")); outputStream.flush(); outputStream.close(); }

 

//自定义过滤器
public
class JwtAuthenticationFilter extends BasicAuthenticationFilter { @Autowired JwtUtils jwtUtils; @Autowired UserDetailServiceImpl userDetailService; @Autowired SysUserService sysUserService; @Autowired RedisUtil redisUtil; public JwtAuthenticationFilter(AuthenticationManager authenticationManager) { super(authenticationManager); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { String jwt = request.getHeader(jwtUtils.getHeader()); if (StrUtil.isBlankOrUndefined(jwt)) { chain.doFilter(request, response); return; } Claims claim = jwtUtils.getClaimByToken(jwt); if (claim == null) { throw new JwtException("token 异常"); } if (jwtUtils.verifyToken(claim)) { throw new JwtException("登录状态已过期,请重新登录"); } // 通过这个来验证接口用注解标注中的权限 UsernamePasswordAuthenticationToken 是Principal的实现类,参考 /nav UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(claim.get("userId"), null, userDetailService.getUserAuthority(Long.valueOf(claim.get("userId") .toString()))); SecurityContextHolder.getContext().setAuthentication(token); chain.doFilter(request, response); } }

自定义逻辑控制 (生成token之前检查username是否在数据库中)

@Service
public class UserDetailServiceImpl implements UserDetailsService {

    @Autowired
    SysUserService sysUserService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUserPo sysUserPo =null;
        sysUserPo = sysUserService.getByUsername(username);
        if (sysUserPo == null) {
            sysUserPo =sysUserService.getByPhone(username);
        }
        if (sysUserPo == null) {
            sysUserPo =sysUserService.getByEmail(username);
        }

        if (sysUserPo == null){
            throw new UsernameNotFoundException("用户名或密码不正确");
        }

        return new LoginUser(sysUserPo.getId(), sysUserPo.getUsername(), sysUserPo.getNickname(),sysUserPo.getPassword(), sysUserPo.getPhone(),sysUserPo.getEmail(),getUserAuthority(sysUserPo.getId()));
    }

    /**
     * 获取用户权限信息(角色、菜单权限)
     * @param userId
     * @return
     */
    public List<GrantedAuthority> getUserAuthority(Long userId){

        // 角色(ROLE_admin)、菜单操作权限 sys:user:list
        String authority = sysUserService.getUserAuthorityInfo(userId);  // ROLE_admin,ROLE_normal,sys:user:list,....

        return AuthorityUtils.commaSeparatedStringToAuthorityList(authority);
    }
}

工具类

package com.prophecy.dental_tech.common.utils;

import com.prophecy.dental_tech.common.security.LoginUser;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Data;
import org.apache.xmlbeans.impl.util.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;

@Data
@Component
public class JwtUtils {
    @Autowired
    private RedisUtil redisUtil;


    private String secret = "dental_bsz";
    private String header = "Authorization";


    public byte[] getKey(String secret){
        return Base64.encode(secret.getBytes(StandardCharsets.UTF_8));
    }

    // 生成jwt
    public String generateToken(Long userId, String phone) {
        Date nowDate = new Date();
        Date expireTime = getExpireTime();

        return Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .claim("userId",userId)
                .claim("phone",phone)
                .setSubject(phone)
                .setIssuedAt(nowDate)
                .setExpiration(expireTime)
                .signWith(SignatureAlgorithm.HS512, getKey(secret))
                .compact();
    }


    public Date getExpireTime(){
        LocalDateTime localDateTime = LocalDate.now().plusDays(1).atStartOfDay(); //下一天的凌晨
        Date expireDate = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
        return expireDate;
    }

    public long getRedisExpireTime(){
        return getExpireTime().getTime() - System.currentTimeMillis();
    }

    // 解析jwt
    public Claims getClaimByToken(String jwt) {
        try {
            return Jwts.parser()
                    .setSigningKey(getKey(secret))
                    .parseClaimsJws(jwt)
                    .getBody();
        } catch (Exception e) {
            return null;
        }
    }

    // jwt是否过期
    public boolean verifyToken(Claims claims) {
        return claims.getExpiration().before(new Date());
    }

    private String getToken(HttpServletRequest request) {
        return  request.getHeader(header);
    }
    /**
     * 获取用户身份信息
     *
     * @return 用户信息
     */
    public LoginUser getLoginUser(HttpServletRequest request) {
        // 获取请求携带的令牌
        String token = getToken(request);
        if (StringUtils.isNotEmpty(token)) {
            try {
                Claims claims = getClaimByToken(token);
                // 解析对应的权限以及用户信息
                String uuid = (String) claims.get(Constants.JWT_USER_UUID);
                String redisUserKey = getRedisUserKey(uuid);
                LoginUser user = (LoginUser)redisUtil.get(redisUserKey);
                return user;
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
        return null;
    }

    /**
     * 获取用户身份信息,现在是手机号
     *
     * @return 用户信息
     */
    public String getUsername(HttpServletRequest request) {
        // 获取请求携带的令牌
        String token = getToken(request);
        if (StringUtils.isNotEmpty(token)) {
            try {
                Claims claims = getClaimByToken(token);
                return claims.getSubject();
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
        return null;
    }

    /**
     * 获取用户身份信息,现在是手机号
     *
     * @return 用户信息
     */
    public String getUserId(HttpServletRequest request) {
        // 获取请求携带的令牌
        String token = getToken(request);
        if (StringUtils.isNotEmpty(token)) {
            try {
                Claims claims = getClaimByToken(token);
                return claims.get("userId").toString();
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
        return null;
    }


    public String getRedisUserKey(String key){
        return Constants.REDIS_LOGIN_USER_KEY +key;
    }

    public static void main(String[] args) {
        String userId="1";
        String phone="13246800000";
        LocalDateTime localDateTime = LocalDate.now().plusYears(3).atStartOfDay(); //下一天的凌晨
        Date expireDate = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
        Date nowDate = new Date();
        String key = Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .claim("userId", userId)
                .claim("phone", phone)
                .setSubject(phone)
                .setIssuedAt(nowDate)
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS512, "dental_bsz")
                .compact();

        Claims dental_bsz = Jwts.parser()
                                .setSigningKey("dental_bsz")
                                .parseClaimsJws(key)
                                .getBody();
        System.out.println(key);
        // eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJ1c2VySWQiOiIxIiwicGhvbmUiOiIxMzI0NjgwMDAwMCIsInN1YiI6IjEzMjQ2ODAwMDAwIiwiaWF0IjoxNjMxMjQ2OTQ3LCJleHAiOjE3MjU4OTc2MDB9.xyIuV5ZVJ_9e1EZrj6JE97IZzU6ckzNcg_vnZoa51mcK2v1g3O5DEId4easvB0O_EVtFUd0AbrMCNz1_qjB9HA
    }

}

生成token接口 

1. 校验username password
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(username, password));
2.再调用 jwtUtils.generateToken(loginUser.getUserId(),loginUser.getPhone()); 返回token


进入接口前的权限校验
@PreAuthorize("hasAuthority('sys:dept:delete')"
@PreAuthorize 权限控制的原理 - 简书 (jianshu.com)

posted @ 2022-02-28 10:33  下饭  阅读(940)  评论(0编辑  收藏  举报