资讯专栏INFORMATION COLUMN

Spring MVC + Security 4 初体验(Java配置版)

EastWoodYang / 1426人阅读

摘要:接着,对登录表单进行配置。通过配置表单提交地址,这个地址对应的不需要自己写,会自动拦截提交到此地址请求,将其视为登录请求。配置完登录相关信息之后,接着配置和登出有关的信息。因为采用风格,这里配置响应视图为格式。

spring Version = 4.3.6.RELEASE
springSecurityVersion = 4.2.1.RELEASE
Gradle 3.0 + Eclipse Neno(4.6)

这篇文章同样是使用的Java配置,而非XML配置,如果你对于Java配置的Spring MVC开发还不太熟悉,可以先看我这篇文章。

Authority

创建一个 Authority ,实现自 org.springframework.security.core.GrantedAuthority 类,getAuthority 方法只返回一个表示权限名称的字符串,如 AUTH_USERAUTH_ADMINAUTH_DBA 等。

public class Authority implements GrantedAuthority {
    
    private static final long serialVersionUID = 1L;

    private String authority;

    public Authority() {  }
    public Authority(String authority) {
        this.setAuthority(authority);
    }
    
    @Override
    public String getAuthority() {
        return this.authority;
    }
    public void setAuthority(String authority) {
        this.authority = authority;
    }
}
User

User 类实现自 org.springframework.security.core.userdetails.UserDetails 接口,包含一组权限的集合 authorities

public class User implements UserDetails {
    
    private static final long serialVersionUID = 1L;
    
    private String username;
    private String password;
    private List authorities;
    
    @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;
    }
    
    @Override
    public Collection getAuthorities() {
        return this.authorities;
    }
    public void setAuthorities(List authorities) {
        this.authorities = authorities;
    }
    
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    @Override
    public boolean isEnabled() {
        return true;
    }
}
UserDetailsService

MyUserDetailsService 实现了 org.springframework.security.core.userdetails.UserDetailsServiceloadUserByUsername 方法,该方法根据用户名查询符合条件的用户,若没有找到符合条件的用户,必须抛出 UsernameNotFoundException 异常,而不能返回空。这里可以调用 DAO 层,从数据库查询用户,我为了简单,直接将用户临时放到一个常量内,模拟从数据库查询用户。

@Service
public class MyUserDetailsService implements UserDetailsService {
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List userList = Constants.userList;
        for (int i = 0, len = userList.size(); i < len; i++) {
            User user = userList.get(i);
            if (user.getUsername().equals(username)) {
                return user;
            }
        }
        throw new UsernameNotFoundException("用户不存在!");
    }

}
SecurityConfig

SecurityConfig 类继承 org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapterWebSecurityConfigurerAdapter 提供了一些默认的配置,方便创建一个实例。

进入 configure 方法中,首先允许任何情况下的对csrfTokenApi 的请求,该 API 返回一个 csrfToken ,默认情况下除 GETHEADTRACEOPTIONS外,所有请求都必须经过 CSRF 认证。接下来对不同的API请求设置不同的权限,并且确保所有对 /api/ 下的请求都经过了认证。
这里向 access 方法传递的表达式中的权限名称,对应上面提到的 Authority 类中 getAuthority 返回的字符串的值,详细的表达式介绍,请移步至这里。

接着,对登录表单进行配置。通过 loginProcessingUrl 配置表单提交地址,这个地址对应的API不需要自己写,Spring Security 会自动拦截提交到此地址请求,将其视为登录请求。如果希望登录成功后通过服务器转发到其他页面,可以调用 successForwardUrl(String forwardUrl) 方法指定跳转的地址,对应地,指定失败后跳转地址的方法是 failureForwardUrl(String forwardUrl)

这里我使用了RESTful,故不需要配置服务端的转发,而是配置了另外两处:successHandlerfailureHandlersuccessHandler 方法接收一个 AuthenticationSuccessHandler 对象,认证通过之后,Spring Security 将调用该对象的 onAuthenticationSuccess 方法,类似地,failureHandler 方法接收一个 AuthenticationFailureHandler 对象,认证失败之后,将调用该对象的 onAuthenticationFailure 方法。

配置完登录相关信息之后,接着配置和登出有关的信息。和配置登录表单提交地址类似,这里需要配置登出请求提交地址,这里调用 logoutUrl 方法,指定登出的链接地址,该地址和前面提到的 loginProcessingUrl 都不需要自己写,这两个都是全权交由 Spring Security 来处理。当用户请求 logoutUrl 方法指定的地址时,Spring Security 将对用户执行登出操作。和前面提到的 successForwardUrl 类似,这里提供了 logoutSuccessUrl 方法指定登出成功之后转发的地址。不过我用了RESTful,就不再调用此方法,而是调用 logoutSuccessHandler 传入 LogoutSuccessHandler 对象,登出成功后将调用该对象的 onLogoutSuccess 方法。

最后,配置对异常的处理 exceptionHandling ,和上面介绍的 successHandlerfailureHandler 以及 logoutSuccessHandler 差不多,authenticationEntryPoint 接收一个 AuthenticationEntryPoint 对象,当用户请求的操作需要登录时,将抛出 AuthenticationException 异常,并且将该异常传入到 AuthenticationEntryPoint 对象的 commence 方法。
accessDeniedHandler 方法接收一个 AccessDeniedHandler 对象,该对象的 handle 方法将在权限不足时调用。

配置完这些,看 configureGlobalSecurity 方法,给 AuthenticationManagerBuilder 配置一个 UserDetailsService 对象,当用户执行登录时,Spring Security 将调用该对象的 loadUserByUsername 方法,将 username 传入此方法,根据 username 获取一个 UserDetails 对象。

另外,由于不能在数据库中保存明文密码,这里对密码进行 bcrypt 加密后保存,验证密码是否正确时,需要对用户输入的明文密码进行 bcrypt 加密后比较密文是否一致,故这里需要提供一个 BCryptPasswordEncoder 对象。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${api.csrftoken}")
    private String csrfTokenApi;
    
    @Value("${api.login}")
    private String loginApi;
    
    @Value("${api.logout}")
    private String logoutApi;
    
    @Autowired
    private MyUserDetailsService userDetailsService;
    
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers(csrfTokenApi).permitAll()
        .antMatchers("/api/user/**").access("hasAuthority("USER")")
        .antMatchers("/api/admin/**").access("hasAuthority("ADMIN")")
        .antMatchers("/api/dba/**").access("hasAuthority("DBA")")
        .antMatchers("/api/**").fullyAuthenticated()
        .and().formLogin().loginProcessingUrl(loginApi)
        .successHandler(new RestAuthenticationSuccessHandler())
        .failureHandler(new RestAuthenticationFailureHandler())
        .and().logout().logoutUrl(logoutApi)
        .logoutSuccessHandler(new RestLogoutSuccessHandler())
        .and().exceptionHandling().authenticationEntryPoint(new RestAuthenticationEntryPoint())
        .accessDeniedHandler(new RestAccessDeniedHandler());
    }
    
    @Autowired
    public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(11);
    }
}
WebAppConfig

因为采用RESTful风格,这里配置响应视图为json格式。

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "org.xueliang.springsecuritystudy")
@PropertySource({"classpath:config.properties"})
public class WebAppConfig extends WebMvcConfigurerAdapter {
    
    @Bean
    public RequestMappingHandlerAdapter requestMappingHandlerAdapter(@Autowired MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter, @Autowired ContentNegotiationManager mvcContentNegotiationManager) {
        RequestMappingHandlerAdapter requestMappingHandlerAdapter = new RequestMappingHandlerAdapter();
        requestMappingHandlerAdapter.setMessageConverters(Collections.singletonList(mappingJackson2HttpMessageConverter));
        requestMappingHandlerAdapter.setContentNegotiationManager(mvcContentNegotiationManager);
        return requestMappingHandlerAdapter;
    }
    
    @Bean
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        return new MappingJackson2HttpMessageConverter();
    }
    
    /**
     * 设置欢迎页
     * 相当于web.xml中的 welcome-file-list > welcome-file
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addRedirectViewController("/", "/index.html");
    }
}
WebAppInitializer

Spring Security 架构是完全基于标准的 Servlet 过滤器的,这里我们需要在 WebInitializer 中引入 DelegatingFilterProxy 过滤器。

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        servletContext.addFilter("springSecurityFilterChain", new DelegatingFilterProxy("springSecurityFilterChain")).addMappingForUrlPatterns(null, false, "/api/*");
        // 静态资源映射
        servletContext.getServletRegistration("default").addMapping("*.html", "*.ico");
        super.onStartup(servletContext);
    }

    @Override
    protected Class[] getRootConfigClasses() {
        return new Class[] { WebAppConfig.class };
    }

    @Override
    protected Class[] getServletConfigClasses() {
        return null;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] { new CharacterEncodingFilter("UTF-8", true) };
    }
}
Source

本文使用到的项目源码已经放到 Github 上,你可以下载后运行。

原文链接:http://xueliang.org/article/detail/20170302232815082

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/66759.html

相关文章

  • SpringMVC体验--Code-based

    摘要:最后创建了群方便大家交流,可扫描加入,同时也可加我,共同学习共同进步,谢谢 创建项目 创建web项目,使用maven webapp模板进行构建,创建完成后,在pom中引入Spring MVC依赖,如下: org.springframework spring-webmvc 5.0.5.RELEASE javax.servlet ja...

    happyfish 评论0 收藏0
  • 猫头鹰的深夜翻译:为什么要使用Spring Boot?

    摘要:初次使用的人往往会困惑,不知道该使用哪种方法。目前来说,团队推荐使用基于的方法来提供更高的灵活性。配置,从而在应用启动时执行脚本来初始化数据库。目前为止我们没有任何消息需要配置,所以只在文件夹中创建一个空的文件。将配置为,它包含的上下文。 前言 spring是一个用于创建web和企业应用的一个很流行的框架。和别的只关注于一点的框架不同,Spring框架通过投资并组合项目提供了大量的功能...

    Jaden 评论0 收藏0
  • SpringMVC体验--传统方式

    摘要:创建项目创建项目,使用模板进行构建,创建完成后,在中引入依赖,如下引入依赖,使用版本为配置在配置文件中,配置控制器如下指定配置文件,默认为,本例配置从中加载容器启动时加载,若不配置此项 创建项目 创建web项目,使用maven webapp模板进行构建,创建完成后,在pom中引入Spring MVC依赖,如下: org.springframework spr...

    Nosee 评论0 收藏0
  • Spring体系常用项目一览

    摘要:的面向的异常遵从通用的异常层次结构。比如以前常用的框架,现在常用的框架包含许多项目,下面挑一些最常用的出来总结一下。状态是流程中事件发生的地点,在流程中通过转移的方式从一个状态到另一个状态,流程的当前状况称为流程数据。 如今做Java尤其是web几乎是避免不了和Spring打交道了,但是Spring是这样的大而全,新鲜名词不断产生,学起来给人一种凌乱的感觉,我就在这里总结一下,理顺头绪...

    OnlyLing 评论0 收藏0
  • SpringBoot+SpringSecurity+jwt整合及体验

    摘要:进行下一项配置,为了区分必须加入。另起一行,以示尊重。这行代码主要是用于验证,后面再说。然后跑下接口,发现没问题,正常打印,说明主体也在上下文中了。说明这会上下文环境中我们主体不存在。所说以,主体数据生命周期是一次请求。 showImg(https://segmentfault.com/img/bVbtoG1?w=1600&h=900); 原来一直使用shiro做安全框架,配置起来相当...

    dackel 评论0 收藏0

发表评论

0条评论

EastWoodYang

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<