Spring Security学习(二)

2021-02-25 10:25:18 浏览数 (1)

以下配置基于表单登录配置

自定义配置登录页面

代码语言:javascript复制
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin()
             // 自定义页面路径
            .loginPage("/api/login")
            .and()
            .authorizeRequests()
            // 允许/api/login的URL访问 否则浏览器页面将提示重定向次数过多进入死循环
            .antMatchers("/api/login").permitAll()
            .anyRequest()
            .authenticated();
}

自定义登录路径

代码语言:javascript复制
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin()
             .loginPage("/api/login")
            // 自定义登录路径
            .loginProcessingUrl("/api/formlogin") 
            .and()
            .authorizeRequests()
            .antMatchers("/api/login").permitAll()
            .anyRequest()
            .authenticated();
}

Spring Security 默认表单登录

代码语言:javascript复制
public UsernamePasswordAuthenticationFilter() {
    super(new AntPathRequestMatcher("/login", "POST"));
}

可设置loginProcessingUrl属性来替换默认登录地址

登录成功处理

当用户登录成功后需要保存用户登录数据,比如IP地址,登录时间等

代码语言:javascript复制
// 注入SecurityLoginSuccessHandler
@Autowired
private SecurityLoginSuccessHandler securityLoginSuccessHandler; 

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin()
            .loginPage("/api/login")
            // 自定义登录成功处理
            .successHandler(securityLoginSuccessHandler)
            .loginProcessingUrl("/api/formlogin")
            .and()
            .authorizeRequests()
            .antMatchers("/api/login","/static/*").permitAll()
            .anyRequest()
            .authenticated()
            .and()
            .csrf().disable();
}

SecurityLoginSuccessHandler

代码语言:javascript复制
@Component
public class SecurityLoginSuccessHandler implements AuthenticationSuccessHandler {
    // 重定向跳转类
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
    // 缓存请求路径,可获取拦截前的请求路径
    private RequestCache requestCache = new HttpSessionRequestCache();

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        System.out.println("登录成功");
        // 获取拦截前请求路径 并通过RedirectStrategy进行重定向跳转
        SavedRequest savedRequest = requestCache.getRequest(request, response);
        String redirectUrl = savedRequest.getRedirectUrl();
        
         // todo do something
        
        this.redirectStrategy.sendRedirect(request, response, redirectUrl);
    }
}

登录失败处理

跟登录成功处理一样 只是实现接口不同而已

代码语言:javascript复制
@Autowired
private SecurityLoginFailHandler securityLoginFailHandler;
    
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin()
            .loginPage("/api/login")
            .successHandler(securityLoginSuccessHandler)
            // 自定义登录失败处理
            .failureHandler(securityLoginFailHandler)
            .loginProcessingUrl("/api/formlogin")
            .and()
            .authorizeRequests()
            .antMatchers("/api/login", "/static/*").permitAll()
            .anyRequest()
            .authenticated()
            .and()
            .csrf().disable();
}

SecurityLoginFailHandler

代码语言:javascript复制
@Component
public class SecurityLoginFailHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        System.out.println("登录失败");
        
        // todo do something
        
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/json;charset=UTF-8");
        response.getWriter().write(exception.getMessage());
    }
}

退出登录

默认是访问URL/logout将注销登陆的用户

自定义配置

代码语言:javascript复制
@Autowired
private SecurityLoginoutHandler securityLoginoutHandler;
@Autowired
private SecurityLoginoutSuccessHandler securityLoginoutSuccessHandler;

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin()
            .loginPage("/api/login")
            .successHandler(securityLoginSuccessHandler)
            .failureHandler(securityLoginFailHandler)
            .loginProcessingUrl("/api/formlogin")
            .and()
            .authorizeRequests()
            .antMatchers("/api/login", "/static/*").permitAll()
            .anyRequest()
            .authenticated()
            .and()
            .logout()
            // 退出登录地址
            .logoutUrl("/logout")
            // 退出跳转路径
            .logoutSuccessUrl("/api/login")
            // 默认session失效
            .invalidateHttpSession(true)
            // 退出登录处理
            .addLogoutHandler(securityLoginoutHandler)
            // 退出登录成功处理
            .logoutSuccessHandler(securityLoginoutSuccessHandler)
            // 指定退出登录后需要删除的cookie名称,多个cookie之间以逗号分隔。 
//                .deleteCookies(cookieNamesToClear)
            .and()
            .csrf().disable();
}

SecurityLoginoutHandler

用来执行必要的清理,因而他们不应该抛出错误

代码语言:javascript复制
@Component
public class SecurityLoginoutHandler implements LogoutHandler {
    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        System.out.println("退出登录");
    }
}

SecurityLoginoutSuccessHandler

被LogoutFilter在成功注销后调用,用来进行重定向或者转发相应的目的地。注意这个接口与LogoutHandler几乎一样,但是可以抛出异常。

代码语言:javascript复制
@Component
public class SecurityLoginoutSuccessHandler implements LogoutSuccessHandler {
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        System.out.println("退出登录成功执行");
        this.redirectStrategy.sendRedirect(request, response, "/");
    }
}

自定义用户登录账号密码

WebSecurityConfig

代码语言:javascript复制
@Autowired
private SecurityUserDetailService securityUserDetailService;

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    // 在内存中写死用户数据
    auth.inMemoryAuthentication().withUser("123456").password("123456").roles("USER");
    // 动态获取用户数据
    auth.userDetailsService(securityUserDetailService);
}

SecurityUserDetailService

代码语言:javascript复制
/**
 * 自定义UserDetailService
 *
 * @author Peng
 */
@Component
public class SecurityUserDetailService implements UserDetailsService {
    /**
     * String password; 密码
     * String username; 账户名
     * Set authorities; 角色名
     * boolean accountNonExpired; 账户没有过期
     * boolean accountNonLocked; 帐号没有被锁定
     * boolean credentialsNonExpired; 密码没有过期
     * boolean enabled; 是否可用
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    // 这里写死的密码 实际应用中应该从数据库中获取 
        User user = new User(username, "123456", true,
                true,
                true,
                true,
                AuthorityUtils.commaSeparatedStringToAuthorityList("USER"));
        return user;
    }
}

自定义用户登录账号密码(密码加密)

WebSecurityConfig

代码语言:javascript复制
@Autowired
private SecurityUserDetailService securityUserDetailService;

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    // 在内存中写死用户数据
    auth.inMemoryAuthentication().withUser("123456").password("123456").roles("USER");
    // 动态获取用户数据 使用Security加密
    auth.userDetailsService(securityUserDetailService).passwordEncoder(new BCryptPasswordEncoder());
     
    // 配置验证方式为加密验证 
    DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
    authenticationProvider.setUserDetailsService(securityUserDetailService);
    authenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());
    auth.authenticationProvider(authenticationProvider);
}

SecurityUserDetailService

代码语言:javascript复制
/**
 * 自定义UserDetailService
 *
 * @author Peng
 */
@Component
public class SecurityUserDetailService implements UserDetailsService {
    /**
     * String password; 密码
     * String username; 账户名
     * Set authorities; 角色名
     * boolean accountNonExpired; 账户没有过期
     * boolean accountNonLocked; 帐号没有被锁定
     * boolean credentialsNonExpired; 密码没有过期
     * boolean enabled; 是否可用
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 这里写死的密码 实际应用中应该从数据库中获取 
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        // 数据库创建用户数据时密码应通过Security加密后保存
        String password = encoder.encode("123456");
        System.out.println(password);
        User user = new User(username, password, true,
                true,
                true,
                true,
                AuthorityUtils.commaSeparatedStringToAuthorityList("USER"));
        return user;
    }
}

PasswordEncoder

代码语言:javascript复制
// 密码加密
String encode(CharSequence rawPassword);
// 验证密码是否吻合
boolean matches(CharSequence rawPassword, String encodedPassword);

获取登录用户信息

代码语言:javascript复制
/**
 * 获取登录用户数据
 */
@RequestMapping("/api/getme")
@ResponseBody
public Object getLoginUser(Authentication authentication) {
    Map<String, Object> json = new HashMap<>();
    // 两种方法
    json.put("data", SecurityContextHolder.getContext().getAuthentication());
    json.put("data1", authentication);
    return json;
}

资源权限限制

WebSecurityConfig

代码语言:javascript复制
 .antMatchers("/api/me").hasRole("ADMIN")
 
 这样只要是访问“api/me”的路径就会验证用户身份,用户身份必须是ADMIN才允许访问

表达

描述

hasRole([role])

如果当前主体具有指定的角色,则返回true.

hasAnyRole([role1,role2])

如果当前的主体有任何提供的角色(给定的作为一个逗号分隔的字符串列表)的话,返回true

hasAuthority([authority])

如果当前的主体具有指定的权限,则返回 true.

hasAnyAuthority([authority1,authority2])

如果当前的主体有任何提供的角色(给定的作为一个逗号分隔的字符串列表)的话,返回true.

principal

允许直接访问表示当前用户的主对象

permitAll

允许所有用户访问

denyAll

不允许用户访问

isAnonymous()

如果当前的主体是一个匿名用户,则返回true.

isRememberMe()

如果当前的主体是一个匿名用户,则返回true

isAuthenticated()

如果用户不是匿名的,则返回 true

isFullyAuthenticated()

如果用户不是一个匿名的或是一个记住我的用户返回true

hasPermission(Object target, Object permission)

如果用户已访问给定权限的提供的目标,则返回true,例如hasPermission(domainObject, 'read')

hasPermission(Object targetId, String targetType, Object permission)

如果用户已访问给定权限的提供的目标,则返回true,例如hasPermission(1, 'com.example.domain.Message', 'read')

登录验证码

首先得写一个获取验证码方法,方法能够生成随机验证码并且把验证码存入session供验证的功能,这里就不写了

因为Spring Security未提供验证码的接口,所以需要我们自己写一个过滤器处理

VaildCodeFilter 验证码校验过滤器

代码语言:javascript复制
/**
 * Security 登录验证码验证
 *
 * @author Peng
 */
public class VaildCodeFilter extends OncePerRequestFilter {
    private SecurityLoginFailHandler securityLoginFailHandler;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 登录路径必须为/api/formlogin才生效
        if ("/api/formlogin".equals(request.getRequestURI())) {
            HttpSession session = request.getSession();
            try {
                String code = request.getParameter(页面验证码字段);
                String sessionCode = (String) session.getAttribute(验证码sessionId);
                if (code == null || sessionCode == null) {
                    throw new VaildCodeException("验证码不存在");
                }
                if (!sessionCode.equals(code)) {
                    throw new VaildCodeException("验证码不匹配");
                }
            } catch (VaildCodeException e) {
                securityLoginFailHandler.onAuthenticationFailure(request, response, e);
                return;
            }
            session.removeAttribute(验证码sessionId);
        }
        filterChain.doFilter(request, response);
    }

    public void setSecurityLoginFailHandler(SecurityLoginFailHandler securityLoginFailHandler) {
        this.securityLoginFailHandler = securityLoginFailHandler;
    }
}

最后在WebSecurityConfig configure中配置

代码语言:javascript复制
  @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 在UsernamePasswordAuthenticationFilter过滤器前添加自定义验证码过滤器
        VaildCodeFilter vaildCodeFilter = new VaildCodeFilter();
        vaildCodeFilter.setSecurityLoginFailHandler(securityLoginFailHandler);
        http.addFilterBefore(vaildCodeFilter, UsernamePasswordAuthenticationFilter.class);
        
        http.formLogin().loginPage("/api/login")
                .successHandler(securityLoginSuccessHandler)
                .failureHandler(securityLoginFailHandler)
                .loginProcessingUrl("/api/formlogin");
        http.authorizeRequests()
                .antMatchers("/api/login", "/static/*", "/api/codeImg").permitAll()
                .antMatchers("/api/me").hasRole("ADMIN")
                .anyRequest()
                .authenticated();
        http.logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/api/login")
                .invalidateHttpSession(true)
                .addLogoutHandler(securityLoginoutHandler)
                .logoutSuccessHandler(securityLoginoutSuccessHandler);
        // 指定退出登录后需要删除的cookie名称,多个cookie之间以逗号分隔。
//                .deleteCookies(cookieNamesToClear)
        http.csrf().disable();
    }

0 人点赞