Spring MVC + Security 4 Initial Experience (Java Configuration Edition)

Keywords: Java Spring Database xml

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

This article is also about Java configuration, not XML configuration. If you are not familiar with Spring MVC development of Java configuration, you can read me first. This article.

Authority

Create an Authority and implement it from the org. spring framework. security. core. GrantedAuthority class. The getAuthority method returns only one string representing the permission name, such as AUTH_USER, AUTH_ADMIN, AUTH_DBA, etc.

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

The User class is implemented from the org. spring framework. security. core. userdetails. UserDetails interface and contains a set of authorities with permissions.

public class User implements UserDetails {
    
    private static final long serialVersionUID = 1L;
    
    private String username;
    private String password;
    private List<Authority> 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<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }
    public void setAuthorities(List<Authority> 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

MyUserDetails Service implements the load UserByUsername method of org. spring framework. security. core. userdetails. UserDetails Service. This method queries qualified users according to their username. If no qualified users are found, the UsernameNotFoundException exception must be thrown instead of returning empty. Here we can call the DAO layer to query users from the database. For the sake of simplicity, I put users temporarily into a constant to simulate querying users from the database.

@Service
public class MyUserDetailsService implements UserDetailsService {
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List<User> 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("Users do not exist!");
    }

}

SecurityConfig

The Security Config class inherits the org. spring framework. security. config. annotation. web. configuration. WebSecurity Configurer Adapter, which provides default configurations for creating an instance.

In the configure method, requests to csrfToken Api are first allowed under any circumstances, and the API returns a csrfToken, which by default must pass all requests except GET, HEAD, TRACE and OPTIONS. CSRF Authentication. Next, set different permissions for different api requests and ensure that all requests under / api / are authenticated.
The permission name in the expression passed to the access method here corresponds to the value of the string returned by get Authority in the Autohority class mentioned above. For a detailed description of the expression, please move to Here.

Next, the login form is configured. The login Processing Url configuration form submits the address. The API corresponding to this address does not need to write by itself. Spring Security automatically intercepts the submission request to this address and treats it as a login request. If you want to forward to other pages through the server after successful login, you can call the success Forward Url (String forward Url) method to specify the address of the jump. Correspondingly, the method of specifying the jump address after failure is failure Forward Url (String forward Url).

Here I use RESTful, so instead of configuring server-side forwarding, I configure two other places: successHandler and failureHandler. The successHandler method receives an AuthenticationSuccessHandler object. After authentication, Spring Security will call the onAuthenticationSuccess method of the object. Similarly, the failureHandler method receives an AuthenticationSuccess object. The nticationFailureHandler object, after authentication fails, calls the onAuthenticationFailure method of the object.

After configuring login related information, configure and log out related information. Similar to configuring the login form submission address, we need to configure the login request submission address. Here we call the logoutUrl method to specify the login process url. This address and the loginProcessingUrl mentioned above do not need to write by themselves. Both of them are handled by Spring Security. When the user requests the address specified by the logoutUrl method, Spring Security performs a logout operation on the user. Similar to the successful ForwardUrl mentioned earlier, the logoutSuccessUrl method is provided here to specify the forwarding address after successful logout. However, with RESTful, instead of calling this method, I call logoutSuccessHandler to pass in the LogoutSuccessHandler object, which will be called onLogoutSuccess method when logout is successful.

Finally, configure exception handling for exception handling, similar to the successful handler, failureHandler and logoutSuccessHandler described above. Authentication EntryPoint receives an Authentication EntryPoint object and throws an Authentication Exception exception into Authentication EntryPoint when the user requests an operation that requires login. The commence method of the tryPoint object.
The accessDeniedHandler method receives an AccessDeniedHandler object, and the handle method of the object is called when the privileges are insufficient.

After configuring these, look at the configureGlobalSecurity method and configure the Authentication Manager Builder with a UserDetails Service object. When the user executes login, Spring Security will call the loadUserByUsername method of the object, pass the username into this method, and get a UserDetails object according to the Detarname.

In addition, because the plaintext password can not be saved in the database, the password is encrypted by bcrypt and saved here. When verifying the correctness of the password, it is necessary to encrypt the user's plaintext password by bcrypt and compare the ciphertext consistency. Therefore, we need to provide a BCrypt PasswordEncoder object.

@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

Because of the RESTful style, the response view is configured in json format.

@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();
    }
    
    /**
     * Setting Welcome Page
     * Equivalent to welcome-file-list > welcome-file in web.xml
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addRedirectViewController("/", "/index.html");
    }
}

WebAppInitializer

Spring Security architecture is based entirely on standard Servlet filters, where we need to introduce Delegating Filter Proxy filters in Web Initializer.

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        servletContext.addFilter("springSecurityFilterChain", new DelegatingFilterProxy("springSecurityFilterChain")).addMappingForUrlPatterns(null, false, "/api/*");
        // Static resource mapping
        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

The project source code used in this article has been put in Github Up, you can download and run.

Links to the original text: http://xueliang.org/article/detail/20170302232815082

Posted by phpform08 on Sat, 13 Apr 2019 19:24:37 -0700