SpringBoot 集成 Spring Security 自定义认证逻辑备忘

引入 Spring Security 之后(Spring Security 的引入请浏览《SpringBoot 引入 Spring Security 备忘》),Spring Security 将会通过其内置的拦截器对URL进行拦截,以此来管理登录验证和用户权限验证。

当用户登陆时,会被 AuthenticationProcessingFilter 拦截,调用 AuthenticationManager 的实现类来验证用户信息,而 AuthenticationManager 是通过调用 AuthenticationProvider 的实现类来获取用户验证信息, 在 AuthenticationProvider 的实现类中将获取到的用户验证信息与通过 UserDetailsService 的实现类根据用户名称获取到的结果(UserDetails 的实现类)进行匹配验证,如果验证通过后会将用户的权限信息封装一个当前登录用户信息对象 放到 Spring Security 的全局缓存 SecurityContextHolder 中,以备后面访问资源时使用。

因此当我们自定义用户认证逻辑的时候,我们需要编写 AuthenticationProvider、UserDetailsService 和 UserDetails 的实现类,现在开始写代码

1、UserDetails 的实现类 SecurityUserInfo

public class SecurityUserInfo implements Serializable, UserDetails {

    private static final long serialVersionUID = 1L;
    /**
     * 用户名
     */
    private String username;
    /**
     * 密码
     */
    private String password;
    /**
     * 盐加密
     */
    private String salt;
    /**
     * 角色码
     */
    private String roleCode;
    /**
     * 帐号状态(0正常 1停用)
     */
    private String status;
    /**
     * 删除状态(0正常 1删除)
     */
    private String delFlag;
    private boolean accountNonExpired;
    private boolean accountNonLocked;
    private boolean credentialsNonExpired;
    private boolean enabled;

    public SecurityUserInfo(String username, String password, String salt, String roleCode, String status, String delFlag, 
            boolean accountNonExpired, boolean accountNonLocked, boolean credentialsNonExpired, boolean enabled) {
        this.username = username;
        this.password = password;
        this.salt = salt;
        this.roleCode = roleCode;
        this.status = status;
        this.delFlag = delFlag;
        this.accountNonExpired = accountNonExpired;
        this.accountNonLocked = accountNonLocked;
        this.credentialsNonExpired = credentialsNonExpired;
        this.enabled = enabled;
    }

    /**
     * 实现用户权限获取方法
     *
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // TODO Auto-generated method stub
        return AuthorityUtils.commaSeparatedStringToAuthorityList(roleCode);
    }

    @Override
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getSalt() {
        return salt;
    }

    public void setSalt(String salt) {
        this.salt = salt;
    }

    public String getRoleCode() {
        return roleCode;
    }

    public void setRoleCode(String roleCode) {
        this.roleCode = roleCode;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getDelFlag() {
        return delFlag;
    }

    public void setDelFlag(String delFlag) {
        this.delFlag = delFlag;
    }

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

    public void setAccountNonExpired(boolean accountNonExpired) {
        this.accountNonExpired = accountNonExpired;
    }

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

    public void setAccountNonLocked(boolean accountNonLocked) {
        this.accountNonLocked = accountNonLocked;
    }

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

    public void setCredentialsNonExpired(boolean credentialsNonExpired) {
        this.credentialsNonExpired = credentialsNonExpired;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }
}

2、UserDetailsService 的实现类 SecurityUserDetailsService

@Component
public class SecurityUserDetailsService implements UserDetailsService {

    @Autowired
    private ISystemUserService userService;

    /**
     * 返回 Spring Security 用户对象
     *
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        /**  username(登录时输入的用户名)然后到数据库中找到对应的用户信息,并构建成我们自己的UserInfo来返回 */
        SystemUser user = userService.selectUserByLoginName(username);
        if(StringUtils.objectIsNotNull(user)) {
            SecurityUserInfo userInfo = new SecurityUserInfo(user.getLoginName(), user.getPassword(), user.getSalt(), user.getRoleCode(),
                    user.getStatus(), user.getDelFlag(),true,true,true, true);
            return userInfo;
        }

        return null;
    }
}

3、AuthenticationProvider 的实现类 SecurityAuthenticationProvider

@Component
public class SecurityAuthenticationProvider implements AuthenticationProvider {

    private Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 注入我们自己定义的用户信息获取对象
     */
    @Autowired
    private UserDetailsService userDetailService;

    /**
     * 认证用户信息
     *
     * @param authentication
     * @return
     * @throws AuthenticationException
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String userName = authentication.getName();// 这个获取表单输入中的用户名
        String password = (String) authentication.getCredentials();// 这个是表单中输入的密码

        /** 判断用户是否存在 */
        SecurityUserInfo userInfo = (SecurityUserInfo) userDetailService.loadUserByUsername(userName); // 这里调用我们的自己写的获取用户的方法;
        if (userInfo == null) {
            throw new UsernameNotFoundException("用户不存在");
        }

        /** 判断密码是否正确 */
        try {
            String encodePwd = MD5.encode(password.concat(userInfo.getSalt()));
            if (!userInfo.getPassword().equals(encodePwd)) {
                throw new BadCredentialsException("密码不正确");
            }
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        /** 判断账号是否停用/删除 */
        if (SystemUserConstants.STOP.equals(userInfo.getStatus()) || SystemUserConstants.DELETED.equals(userInfo.getStatus())) {
            throw new DisabledException("账户不可用");
        }

        Collection<? extends GrantedAuthority> authorities = userInfo.getAuthorities();
        return new UsernamePasswordAuthenticationToken(userInfo, password, authorities);// 构建返回的用户登录成功的token
    }

    /**
     * 执行支持判断
     *
     * @param authentication
     * @return
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return true;// 返回 true ,表示支持执行
    }
}

4、到这边为止,其实自定义的用户认证逻辑已经完成,但是为了后续能够在登录页面显示认证错误信息以及登录成功后记录用户登录日志、为用户授权等操作,还需要自定义登录失败和成功的处理逻辑。

5、用户登录失败处理逻辑 SecurityAuthenticationFailHandler

@Component("securityAuthenticationFailHandler")
public class SecurityAuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Value("${security.user.failureUrl}")
    private String failureUrl;// 权限认证失败地址

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception) throws IOException, ServletException {
        logger.debug(exception.getMessage() + " " + failureUrl);
        /** 跳转到指定页面 */
        String redirectUrl = "/login-error?message=" + URLEncoder.encode(exception.getMessage(),"UTF-8");
        new DefaultRedirectStrategy().sendRedirect(request, response, redirectUrl);
    }
}

6、用户登录成功处理逻辑 SecurityAuthenticationSuccessHandler

@Component("securityAuthenticationSuccessHandler")
public class SecurityAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 成功处理
     *
     * @param request
     * @param response
     * @param authentication
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
            throws IOException, ServletException {
        logger.debug("登录成功");
        /*
        super.onAuthenticationSuccess(request, response, authentication);// 直接调用父类方法
        */

        /*
        Map<String,String> map=new HashMap<>();
        map.put("code", "200");
        map.put("msg", "登录成功");
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(map));// 返回 JSON 信息
        */
        
        // TODO 用户登录日志、链接权限关联

        /** 跳转到指定页面 */
        new DefaultRedirectStrategy().sendRedirect(request, response, "/security");

    }
}

7、最后修改 Spring Security 的配置文件 SecurityConfig

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 注入自定义的 AuthenticationProvider
     */
    @Autowired
    private AuthenticationProvider provider;
    /**
     * 注入自定义的 AuthenticationSuccessHandler
     */
    @Autowired
    private AuthenticationSuccessHandler securityAuthenticationSuccessHandler;
    /**
     * 注入自定义的 AuthenticationFailureHandler
     */
    @Autowired
    private AuthenticationFailureHandler securityAuthenticationFailHandler;

    /**
     * 访问权限配置
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .formLogin().loginPage("/login").loginProcessingUrl("/login-process")
                .successHandler(securityAuthenticationSuccessHandler)
                .failureHandler(securityAuthenticationFailHandler)
                .permitAll()  // 登录页面链接、登录表单链接、登录失败页面链接配置
                .and()
                .authorizeRequests()
                .antMatchers("/ace/**").permitAll() // 静态资源配置
                .antMatchers("/index", "/login-error").permitAll() // 免校验链接配置
                .anyRequest().authenticated()
                .and()
                .csrf().disable();
    }

    /**
     * 动态认证
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(provider);
    }


    /**
     * 静态认证
     *
     * @param auth
     * @throws Exception
     */
    /*@Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        logger.debug("Spring Security 用户认证!");
        auth
                .inMemoryAuthentication()
                .passwordEncoder(new BCryptPasswordEncoder()).withUser("user").password(new BCryptPasswordEncoder().encode("123456")).roles("USER")
                .and()
                .passwordEncoder(new BCryptPasswordEncoder()).withUser("system").password(new BCryptPasswordEncoder().encode("123456")).roles("ADMIN");
    }*/
}

8、登录页面 html 表单

<form action="/login-process" method="post">
   <fieldset>
      <label class="block clearfix">
         <span class="block input-icon input-icon-right">
            <input type="text" name="username" class="form-control" placeholder="用户名" />
            <i class="ace-icon fa fa-user"></i>
         </span>
      </label>

      <label class="block clearfix">
         <span class="block input-icon input-icon-right">
            <input type="password" name="password" class="form-control" placeholder="密码" />
            <i class="ace-icon fa fa-lock"></i>
         </span>
      </label>

      <div class="space"></div>

      <div class="clearfix">
         <label class="inline">
            <input type="checkbox" class="ace" />
            <span class="lbl"> 记住我</span>
         </label>

         <button type="submit" class="width-35 pull-right btn btn-sm btn-primary">
            <i class="ace-icon fa fa-key"></i>
            <span class="bigger-110">登录</span>
         </button>
      </div>
      <div class="space-4"></div>
   </fieldset>
</form>

自定义权限认证移至:SpringBoot 集成 Spring Security 自定义权限逻辑备忘

猜你喜欢

转载自blog.csdn.net/nangongyanya/article/details/82150848