Spring Security从单体应用到分布式(二)-单体应用引入Spring Security

1.引入依赖

笔者选择使用Spring Boot,版本为1.4.5.RELEASE,所以只需要引入下面两个依赖。

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

2.配置Spring Security

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();//禁止csrf
        http.formLogin();//使用表单登录
        http.exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/loginpage"));
        http.addFilter(customAuthenticationFilter());
        http.authorizeRequests().antMatchers("/home").permitAll();
        http.authorizeRequests().antMatchers("/**").authenticated();

    }

    public CustomUserDetialService customUserDetialService(){
        return new CustomUserDetialService();
    }

    private UsernamePasswordAuthenticationFilter customAuthenticationFilter()
    {
        UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter=new UsernamePasswordAuthenticationFilter();
        usernamePasswordAuthenticationFilter.setUsernameParameter("user");//设置校验前端用户名参数命名
        usernamePasswordAuthenticationFilter.setPasswordParameter("pwd");//设置校验前端密码参数命名
        usernamePasswordAuthenticationFilter.setAuthenticationManager(customAuthenticationManager());
        usernamePasswordAuthenticationFilter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler("/helloworld"));//成功Handler在后面分布式中还会继续分析
        usernamePasswordAuthenticationFilter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/fail"));
        usernamePasswordAuthenticationFilter.setFilterProcessesUrl("/loginTest");//校验
        return usernamePasswordAuthenticationFilter;
    }

    private AuthenticationProvider customProvider(){
        CustomProvider customProvider = new CustomProvider();
        customProvider.setUserDetailsService(customUserDetialService());
        return customProvider;
    }


    private AuthenticationManager customAuthenticationManager(){
        List<AuthenticationProvider> authenticationProviders=new ArrayList<AuthenticationProvider>();
        authenticationProviders.add(customProvider());
        return new ProviderManager(authenticationProviders);
    }
}

2.1 @EnableWebSecurity

必要注解使拥有这个配置的服务用上Spring Security

2.2  @EnableGlobalMethodSecurity

启动Spring Security的注解功能,通过注解实现权限功能,使用prePostEnabled,详细可以参考https://blog.csdn.net/w605283073/article/details/51327182

2.3 configure(HttpSecurity http)服务

源码中该方法的默认配置为http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic(),好明显这个配置是无法满足实际开发需要,在实际开发中需要进行改写。http是支持链式写法的,但是如果使用链式写法可读性会变得很差,所以我写成一行一个功能配置

http.csrf().disable():禁止csrf
http.formLogin():允许使用表单登录
http.exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/loginpage")):定义了在鉴权过程中出错的表现,像目前的设置就是如果在使用过程中出现鉴权问题那么就会跳到登录页面
http.addFilter(customAuthenticationFilter()):添加我们自定义的过滤器
在ExpressionUrlAuthorizationConfigurer中有很多关于url的配置,但是实际开发中我觉得写上的就只有permitAll() authenticated(),甚至不会使用anonymous()。
antMatchers()能接受字符串数组
http.authorizeRequests().antMatchers("/login1").permitAll():antMatchers中的路径都不会进行拦截
http.authorizeRequests().antMatchers("/**").authenticated():antMatchers中的路径会进行鉴权判断

2.4 UsernamePasswordAuthenticationFilter

在实际开发中很多时候会有自定义的一些处理,在这个demo就定义了一个自定义的过滤器去执行我们的逻辑

UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter=new UsernamePasswordAuthenticationFilter();
usernamePasswordAuthenticationFilter.setUsernameParameter("user"):设置校验前端用户名参数命名
usernamePasswordAuthenticationFilter.setPasswordParameter("pwd"):设置校验前端密码参数命名
usernamePasswordAuthenticationFilter.setAuthenticationManager(customAuthenticationManager())
usernamePasswordAuthenticationFilter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler("/helloworld")):成功的handler
usernamePasswordAuthenticationFilter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/home")):失败后的handler
usernamePasswordAuthenticationFilter.setFilterProcessesUrl("/loginTest"):设置了spring security登录的验证路口

3 测试及相关组件分析

不校验/home

因为在配置阶段设置了除/home以外所有链接都要经过登录校验,所以访问下图跳转到/loginpage

上一篇文章Spring Security从单体应用到分布式(一)-基本介绍,介绍自定义登录方式需要实现的接口CustomUserDetialService 实现了UserDetailsService 接口,在实际开发中这里会通过传入的username来查询出对应带有用户权限的用户

public class CustomUserDetialService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List<GrantedAuthority> userRole=new ArrayList();
        if("stephanie".equals(username))
        {
            CustomUser customUser=new CustomUser("stephanie","12345678");
            userRole.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
            customUser.setAuthorities(userRole);
            return customUser;
        }
        else
        {
            CustomUser customUser=new CustomUser("normaluser","12345678");
            userRole.add(new SimpleGrantedAuthority("ROLE_USER"));
            customUser.setAuthorities(userRole);
            return customUser;
        }
    }
}
CustomProvider重写AbstractUserDetailsAuthenticationProvider的additionalAuthenticationChecks方法,定义了属于自己的密证方式
public class CustomProvider extends AbstractUserDetailsAuthenticationProvider {

    private UserDetailsService userDetailsService;

    public void setUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        if(!userDetails.getPassword().equals(authentication.getCredentials()))
            throw new InternalAuthenticationServiceException("User not found");
    }

    @Override
    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        UserDetails userDetails=null;
        try{
            userDetails = this.userDetailsService.loadUserByUsername(username);

        }catch (Exception ex)
        {
            throw new InternalAuthenticationServiceException(ex.getMessage());
        }
        if(userDetails==null)
        {
            throw new InternalAuthenticationServiceException("User not found");
        }
        return userDetails;
    }
}

验证通过

扫描二维码关注公众号,回复: 3645891 查看本文章

验证失败

权限控制

在前面的代码中只有一个用户拥有ADMIN权限,而其他用户都只有USER权限,这里要注意在给UserDetails 添加用户权限的时候需要给每个权限添加ROLE_的前缀,这是因为ExpressionUrlAuthorizationConfigurer的hasRole方法中会自动添加ROLE_前缀,并且生成"hasRole('ROLE_" + role + "')"的注解配置,当配置使用PreAuthorize注解中给予一个权限表达式的时候,只有拥有这个权限的用户才可以进行访问

@RestController
public class SecurityController {
    @RequestMapping("/home")
    public String home(){
        return "home";
    }

    @RequestMapping("/loginpage")
    public String loginpage(){
        return "loginpage";
    }

    @RequestMapping("/helloworld")
    public String helloworld(){
        return "helloworld";
    }

    @RequestMapping("/user")
    @PreAuthorize("hasRole('USER')")
    public String user(){
        return "user";
    }

    @RequestMapping("/admin")
    @PreAuthorize("hasRole('ADMIN')")
    public String admin(){
        return "admin";
    }

    @RequestMapping("/fail")
    public String fail(){
        return "fail";
    }

    @RequestMapping("/authenticattest")
    public String authenticattest(){
        return "authenticattest";
    }


}

git:https://github.com/tale2009/spring-security-learning

猜你喜欢

转载自blog.csdn.net/kiranet/article/details/81784497