使用 Spinrg Security 进行基于用户名密码的安全验证(无权限控制)

笔记

引入起始依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

UserDetails

UserDetails对象是用来存储在Spring Security上下文中,代表当前登录用户信息的。实现此步,只要自定义一个UserDetails类,继承Spring Security提供的User类:
org.springframework.security.core.userdetails.User

示例:

public class UserLoginDetails extends User{

    private static final long serialVersionUID = -360606014621189433L;

    private MUserDTO mUser;

    //mUser为当前用户信息,authorities为当前用户的角色集合
    public UserLoginDetails(MUserDTO mUser,
            Collection<? extends GrantedAuthority> authorities) {
        super(mUser.getUsername(), mUser.getPassword(), authorities);
        this.mUser = mUser;
    }

    public MUserDTO getmUser() {
        return mUser;
    }

    public void setmUser(MUserDTO mUser) {
        this.mUser = mUser;
    }

}

UserDetailsService

UserDetailsService用户获取本系统中的用户信息。具体使用方式为编写一个实现org.springframework.security.core.userdetails.UserDetailsService的类,复写loadUserByUsername(String username)方法,通过此方法根据username查询数据库,将查出的用户信息组成一个UserDetails作为返回值返回。Spring Security会将此用户信息放入上下文中。

示例:

@Service
public class WebUserDetailsService implements UserDetailsService {

    @Autowired
    private MUserService mUserService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        MUserDTO user = mUserService.getByUsernanme(username);
        if(null == user) {
            throw new UsernameNotFoundException("用户不存在");
        }
        //不做角色,以后要的话在这里加载角色,现在返回空List
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        UserLoginDetails details = new UserLoginDetails(user, grantedAuthorities);
        return details;
    }
}

AbstractAuthenticationProcessingFilter

这个过滤器是为了拦截登录url,获取请求中的用户名、密码相应参数,然后生成用户token放到Spring上下文中。

使用到的常量类:

public interface WebLoginConstants {

    /**
     * 登录url
     */
    String LOGIN_URL = "/login";

    /**
     * 登录请求方法
     */
    String WEB_LOGIN_METHOD = "POST";

    /**
     * 用户名参数
     */
    String SPRING_SECURITY_FORM_USERNAME_KEY = "username";

    /**
     * 密码参数
     */
    String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
}

示例:

public class WebUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    protected WebUsernamePasswordAuthenticationFilter() {
        super(new AntPathRequestMatcher(WebLoginConstants.LOGIN_URL, WebLoginConstants.WEB_LOGIN_METHOD));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {
        String username;
        String password;
        try {
            username = obtainUsername(request);
            password = obtainPassword(request);
        } catch (Exception e) {
            throw new AuthenticationServiceException("Authentication parameter not supported ");
        }

        if (username == null)
            username = "";

        if (password == null)
            password = "";

        username = username.trim();
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);

        setDetails(request, authRequest);

        return this.getAuthenticationManager().authenticate(authRequest);

    }

    /**
     * 从request中拿password
     * @param request
     * @return
     */
    protected String obtainPassword(HttpServletRequest request) {
        return request.getParameter(WebLoginConstants.SPRING_SECURITY_FORM_PASSWORD_KEY);
    }


    /**
     * 从request中拿username
     * @param request
     * @return
     */
    protected String obtainUsername(HttpServletRequest request) {
        return request.getParameter(WebLoginConstants.SPRING_SECURITY_FORM_USERNAME_KEY);
    }

    /**
     * 将试图登陆的用户的用户token存到Spring Security上下文
     * @param request
     * @param authRequest
     */
    protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }

}

AbstractUserDetailsAuthenticationProvider

此类用于自定义密码校验,在这个类中,可以对比token中的密码经过处理后是否与UserDetails中的密码一致,如果一致则放行,否则抛出密码错误异常。

简单模拟加密工具:

public class MllPasswordEncoder implements PasswordEncoder {

    private static MllPasswordEncoder encoder = new MllPasswordEncoder();

    public static MllPasswordEncoder getInstance() {
        return encoder;
    }

    private MllPasswordEncoder(){}

    @Override
    public String encode(CharSequence rawPassword) {
        return String.valueOf(rawPassword) + "*";
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        if(rawPassword.equals(String.valueOf(encodedPassword) + "*")){
            return true;
        }
        return false;
    }
}

示例:

@Component
public class WebAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

    @Autowired
    private WebUserDetailsService webUserDetailsService;

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        String presentedPassword = authentication.getCredentials().toString();
        if (!MllPasswordEncoder.getInstance()
                .matches(presentedPassword, userDetails.getPassword())) {
            throw new BadCredentialsException("用户名或密码不正确");
        }
    }

    @Override
    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        UserDetails loadedUser;
        try {
            loadedUser = this.webUserDetailsService.loadUserByUsername(username);
        } catch (UsernameNotFoundException e) {
            throw e;
        } catch (Exception e) {
            throw new InternalAuthenticationServiceException(e.getMessage(), e);
        }

        if (loadedUser == null) {
            throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
        } else {
            return loadedUser;
        }
    }

}

AuthenticationEntryPoint

用于未登录错误的处理。当用户未登录时,会调用此类中的方法:
commence(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,AuthenticationException e)

示例:

@Component
public class WebAuthenticationEntryPoint implements AuthenticationEntryPoint, InitializingBean {

    @Override
    public void commence(HttpServletRequest httpServletRequest,
            HttpServletResponse httpServletResponse,
            AuthenticationException e) throws IOException, ServletException {
        // 直接返回未登录处理
        httpServletResponse.setStatus(401);
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.sendError(401, "用户没有登陆或登陆超时,请重新登陆!");
        httpServletResponse.getWriter().write("用户没有登陆或登陆超时,请重新登陆!");
    }


    @Override
    public void afterPropertiesSet() throws Exception {

    }

}

SimpleUrlAuthenticationFailureHandler

用于用户名密码校验失败的处理,用户token和查询出的用户信息不匹配或不存在时,调用此类中的方法:onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,AuthenticationException exception)

示例:

@Component
public class WebAuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler
        implements AuthenticationFailureHandler {

    /**
     * 登录时密码或账号不正确时的操作
     */
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException exception) throws IOException, ServletException {
        response.setCharacterEncoding("utf-8");
        response.getWriter().write("incorrect");
        return;
    }

}

SavedRequestAwareAuthenticationSuccessHandler

用于用户校验成功后的处理,一般会在用户登录成功进行一些后置方法的执行,比如增加在线数、保存session等等。

示例:

@Component
public class WebAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException {
        System.out.println(SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString());
        UserLoginDetails userDetails = (UserLoginDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        HttpSession session = request.getSession();
        // 让以前的session失效
        session.invalidate();
        // 创建新的session
        session = request.getSession();
        session.setAttribute("SESSION_USER", userDetails);
        session.setAttribute("SESSION_USERID", userDetails.getmUser().getNickname());
        response.setCharacterEncoding("utf-8");
        response.getWriter().write("success");
        return;
    }
}

拼装这些类

使用一个配置类,来将这些配置组装起来,实现个性化的安全验证。

示例:

@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private WebAuthenticationSuccessHandler authenticationSuccessHandler;

    @Autowired
    private WebAuthenticationFailHandler authenticationFailHandler;

    @Autowired
    WebAuthenticationEntryPoint webAuthenticationEntryPoint;

    @Autowired
    WebAuthenticationProvider webAuthenticationProvider;

    @Autowired
    WebLogoutSuccessHandler webLogoutSuccessHandler;

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated()
                .and()
                    .logout().logoutUrl("/logout")
                    .logoutSuccessHandler(webLogoutSuccessHandler)
                    .invalidateHttpSession(true).permitAll()
                .and()
                    .exceptionHandling()
                    .authenticationEntryPoint(webAuthenticationEntryPoint);

        http.addFilterBefore(webUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }

    @Bean
    public WebUsernamePasswordAuthenticationFilter webUsernamePasswordAuthenticationFilter() {
        WebUsernamePasswordAuthenticationFilter filter = new WebUsernamePasswordAuthenticationFilter();
        try {
            filter.setAuthenticationManager(authenticationManagerBean());
        } catch (Exception e) {
            e.printStackTrace();
        }
        filter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
        filter.setAuthenticationFailureHandler(authenticationFailHandler);
        return filter;
    }


}

到此,简单的基于用户名密码的登录验证就完成了。如果需要加入权限控制,则需要在此基础上加入新的配置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值