Spring Security 做token形式登录 和 动态权限管理

文章目录

  • 一、登录
  • 1.实现登录成功后处理器
  • 2.实现登录失败 处理器
  • 3.实现未登录直接访问失败 处理器
  • 4. 实现从数据库读取user ,实现UserDetailsService.loadUserByUsername
  • 5.实现jwt过滤器,重写BasicAuthenticationFilter
  • 6.实现Security 的配置类 ,重写WebSecurityConfigurerAdapter
  • 二、动态权限管理
  • 1. 实现获取当前url的权限标识,重写FilterInvocationSecurityMetadataSource
  • 2.实现自定义验证权限,重写AccessDecisionManager
  • 3.修改配置类,将重写类添加
  • 三、密码加密传输
  • 1.前端加密
  • 2.后端解密
  • 3.修改配置类


一、登录

1.实现登录成功后处理器

@Service("authenticationSuccessHandler")
public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    @Autowired
    private JwtUtils jwtUtils;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response

            , Authentication authentication) throws IOException {

        logger.info("User: " + request.getParameter("username") + " Login successfully.");

        this.returnJson(response,authentication);

    }

    private void returnJson(HttpServletResponse response,Authentication authentication) throws IOException {



        String token = jwtUtils.generateToken(authentication);

        response.setStatus(HttpServletResponse.SC_OK);

        response.setCharacterEncoding("UTF-8");

        response.setContentType("application/json");

        response.getWriter().println("{\"exceptionId\":\"null\",\"messageCode\":\"200\"," +

                "\"message\": \"登录成功\",\"Authorization\": \"" + token + "\"}");

    }

}

2.实现登录失败 处理器

@Service("authenticationFailHandler")
public class AuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler {

    @Override

    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {

        this.returnJson(response, exception);

    }

    private void returnJson(HttpServletResponse response,

                            AuthenticationException exception) throws IOException {

        response.setStatus(HttpServletResponse.SC_OK);

        response.setCharacterEncoding("UTF-8");

        response.setContentType("application/json");

        response.getWriter().println("{\"exceptionId\":\"null\",\"messageCode\":\"401\"," +

                "\"message\": \"" + "用户名或密码错误" + "\",\"serverTime\": " + System.currentTimeMillis() + "}");

    }

}

3.实现未登录直接访问失败 处理器

@Slf4j
@Service("authenticationEntryPointImpl")
class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse response,
                         AuthenticationException e) throws IOException {

        log.error("Spring Securtiy异常", e);
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        PrintWriter out = response.getWriter();
        out.print(JSON.toJSONString(Result.fail(402,"未登录,请先登录!",null)));
    }
}

4. 实现从数据库读取user ,实现UserDetailsService.loadUserByUsername

@Component
public class DatabaseUserDetailsService implements UserDetailsService {

    @Resource
    private TbUserMapper TbUserMapper;

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        QueryWrapper<TbUser> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username",userName);
        TbUser user = TbUserMapper.selectOne(queryWrapper);
        if (user == null) {
            //throw exception inform front end not this user
            throw new UsernameNotFoundException("user + " + userName + "not found.");
        }
        List<String> roleIdList = TbUserMapper.queryUserOwnedRoleIds(userName);

        List<GrantedAuthority> authorities =
                roleIdList.stream().map(e -> new SimpleGrantedAuthority(e)).collect(Collectors.toList());

        UserDetails userDetails = new org.springframework.security.core.userdetails.User(
                user.getUsername(),user.getPassword(),authorities);

        return userDetails;
    }

    public static void main(String[] args) {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String encode = bCryptPasswordEncoder.encode("1234");
        System.out.println(encode);
    }
}

5.实现jwt过滤器,重写BasicAuthenticationFilter

@Slf4j
public class JWTAuthenticationFilter extends BasicAuthenticationFilter {
    
    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        JwtUtils jwtUtils = SpringContextUtil.getBean(JwtUtils.class);
        String jwt = request.getHeader("Authorization");
        if (StrUtil.isBlankOrUndefined(jwt)) {
            chain.doFilter(request, response);
            return;
        }
        Claims claim = jwtUtils.getClaimByToken(jwt);
        if (claim == null) {
            throw new JwtException("token异常!");
        }
        Date expiration = claim.getExpiration();
        String username = claim.getSubject();
        List<String> roleIdList = (List<String>) claim.get("roles");

        if (jwtUtils.isTokenExpired(expiration)) {
            throw new JwtException("token已过期");
        }
        List<GrantedAuthority> authorities =
                roleIdList.stream().map(e -> new SimpleGrantedAuthority(e)).collect(Collectors.toList());

        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
                = new UsernamePasswordAuthenticationToken(username, null , authorities);
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        chain.doFilter(request, response);
    }

}

6.实现Security 的配置类 ,重写WebSecurityConfigurerAdapter

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

    @Autowired
    private DatabaseUserDetailsService userDetailsService;

    @Autowired
    @Qualifier("authenticationSuccessHandler")
    private AuthenticationSuccessHandler successHandler;

    @Autowired
    @Qualifier("authenticationFailHandler")
    private AuthenticationFailHandler failHandler;

    @Autowired
    @Qualifier("authenticationEntryPointImpl")
    private AuthenticationEntryPoint entryPoint;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();   // 使用 BCrypt 加密
    }

    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(userDetailsService);
        authenticationProvider.setPasswordEncoder(passwordEncoder); // 设置密码加密方式
        return authenticationProvider;
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .and().csrf().disable().cors().and()
                .authorizeRequests().requestMatchers(CorsUtils::isPreFlightRequest).authenticated()
                //允许所有人访问knife4j
                .antMatchers("/doc.html", "/webjars/**", "/", "/img.icons/**", "/swagger-resources/**", "/login", "/v2/api-docs").permitAll()
                .anyRequest().authenticated()
                .and().formLogin().loginProcessingUrl("/login")
                .successHandler(successHandler)
                .failureHandler(failHandler)
                .and().logout().logoutSuccessUrl("/login")
                .and().exceptionHandling().authenticationEntryPoint(entryPoint);
        http.addFilterBefore(new JWTAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
    }

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

    @Bean
    public AppFilterInvocationSecurityMetadataSource mySecurityMetadataSource(FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource) {
        AppFilterInvocationSecurityMetadataSource securityMetadataSource = new AppFilterInvocationSecurityMetadataSource(filterInvocationSecurityMetadataSource);
        return securityMetadataSource;
    }
}

二、动态权限管理

1. 实现获取当前url的权限标识,重写FilterInvocationSecurityMetadataSource

public class AppFilterInvocationSecurityMetadataSource implements org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource {

    @Autowired
    private TbRoleService tbRoleService;

    private FilterInvocationSecurityMetadataSource superMetadataSource;

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }


    public AppFilterInvocationSecurityMetadataSource(FilterInvocationSecurityMetadataSource expressionBasedFilterInvocationSecurityMetadataSource){
        this.superMetadataSource = expressionBasedFilterInvocationSecurityMetadataSource;
    }

    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        FilterInvocation fi = (FilterInvocation) object;
        String url = fi.getRequestUrl();
        // TODO 从数据库加载权限配置
        List<PathRoleName> pathRoleNameList=tbRoleService.findPathAndRoleName();

        for (PathRoleName pathRoleName : pathRoleNameList) {
            if (antPathMatcher.match(pathRoleName.getPath(),url)){
                return SecurityConfig.createList(pathRoleName.getRoleName());
            }
        }
        //  返回代码定义的默认配置
        return SecurityConfig.createList("ROLE_LOGIN");
    }


    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }
}

2.实现自定义验证权限,重写AccessDecisionManager

@Component
public class UrlAccessDecisionManager implements AccessDecisionManager {
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
        Iterator<ConfigAttribute> iterator = collection.iterator();
        while (iterator.hasNext()) {
            ConfigAttribute ca = iterator.next();
            //当前请求需要的权限
            String needRole = ca.getAttribute();

            if ("ROLE_LOGIN".equals(needRole)) {
                if (authentication instanceof AnonymousAuthenticationToken) {
                    throw new BadCredentialsException("未登录");
                } else {
                    return;
                }
            }
            //当前用户所具有的权限
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for (GrantedAuthority authority : authorities) {
                if (authority.getAuthority().equals(needRole)) {
                    return;
                }
            }
        }
        throw new AccessDeniedException("权限不足!");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

3.修改配置类,将重写类添加

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .and().csrf().disable().cors().and()
                .authorizeRequests().requestMatchers(CorsUtils::isPreFlightRequest).authenticated()
                //允许所有人访问knife4j
                .antMatchers("/doc.html", "/webjars/**", "/", "/img.icons/**", "/swagger-resources/**", "/login", "/v2/api-docs").permitAll()
                .anyRequest().authenticated()
                .and().formLogin().loginProcessingUrl("/login")
                .successHandler(successHandler)
                .failureHandler(failHandler)
                .and().logout().logoutSuccessUrl("/login")
                .and().exceptionHandling().authenticationEntryPoint(entryPoint)
                .and().authorizeRequests()// 自定义FilterInvocationSecurityMetadataSource
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(
                            O fsi) {
                        fsi.setSecurityMetadataSource(mySecurityMetadataSource(fsi.getSecurityMetadataSource()));
                        fsi.setAccessDecisionManager(urlAccessDecisionManager);
                        return fsi;
                    }
                });
        http.addFilterBefore(new JWTAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
    }

三、密码加密传输

1.前端加密

$.ajax({
        type: "post",
        url: "/login",
        data: {
            "username": username,
            "password": encrypt(password)
        },
        success: function(res) {
        }
    });

2.后端解密

import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
 
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
 
public class SignInPasswordDecryptionFilter extends OncePerRequestFilter {
 
    private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login", HttpMethod.POST.name());
 
    @Override
    protected void doFilterInternal(
            HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        if (DEFAULT_ANT_PATH_REQUEST_MATCHER.matcher(request).isMatch()) {
            filterChain.doFilter(new FormContentRequestWrapper(request), response);
        } else {
            filterChain.doFilter(request, response);
        }
    }
 
 
    private static class FormContentRequestWrapper extends HttpServletRequestWrapper {
 
        public FormContentRequestWrapper(HttpServletRequest request) {
            super(request);
        }
 
        @Override
        @Nullable
        public String getParameter(String name) {
            String queryStringValue = super.getParameter(name);
 
            if (UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_PASSWORD_KEY.equals(name)) {
                // 解密操作
                queryStringValue = "123456";
            }
 
            return queryStringValue;
        }
 
    }
 
}

3.修改配置类

http.addFilterBefore(new SignInPasswordDecryptionFilter(), UsernamePasswordAuthenticationFilter.class);

你可能感兴趣的:(spring,security,java)