zoukankan      html  css  js  c++  java
  • 使用SpringSecurity保护程序安全

    首先,引入依赖:

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

    引入此依赖之后,你的web程序将拥有以下功能:

    • 所有请求路径都需要认证
    • 不需要特定的角色和权限
    • 没有登录页面,使用HTTP基本身份认证
    • 只有一个用户,名称为user

    配置SpringSecurity

    springsecurity配置项,最好保存在一个单独的配置类中:

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        
    }
    

    **配置用户认证方式**

    首先,要解决的就是用户注册,保存用户的信息。springsecurity提供四种存储用户的方式:

    • 基于内存(生产肯定不使用)
    • 基于JDBC
    • 基于LDAP
    • 用户自定义(最常用)

    使用其中任意一种方式,需要覆盖configure(AuthenticationManagerBuilder auth)方法:

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        }
    }
    

    1.基于内存

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("zhangsan").password("123").authorities("ROLE_USER")
                .and()
                .withUser("lisi").password("456").authorities("ROLE_USER");
    }
    

    2.基于JDBC

    @Autowired
    DataSource dataSource;
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication()
            .dataSource(dataSource);
    }
    

    基于JDBC的方式,你必须有一些特定表表,而且字段满足其查询规则:

    public static final String DEF_USERS_BY_USERNAME_QUERY =
        "select username,password,enabled " +
        "from users " +
        "where username = ?";
    public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY =
        "select username,authority " +
        "from authorities " +
        "where username = ?";
    public static final String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY = 
        "select g.id, g.group_name, ga.authority " +
        "from groups g, group_members gm, group_authorities ga " +
        "where gm.username = ? " +
        "and g.id = ga.group_id " +
        "and g.id = gm.group_id";
    

    当然,你可以对这些语句进行一下修改:

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {  
        auth.jdbcAuthentication().dataSource(dataSource)
            .usersByUsernameQuery("select username, password, enabled from Users " +
                                  "where username=?")
            .authoritiesByUsernameQuery("select username, authority from UserAuthorities " +
                                        "where username=?");
    

    这有一个问题,你数据库中的密码可能是一种加密方式加密过的,而用户传递的是明文,比较的时候需要进行加密处理,springsecurity也提供了相应的功能:

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {  
        auth.jdbcAuthentication().dataSource(dataSource)
            .usersByUsernameQuery("select username, password, enabled from Users " +
                                  "where username=?")
            .authoritiesByUsernameQuery("select username, authority from UserAuthorities " +
                                        "where username=?")
            .passwordEncoder(new StandardPasswordEncoder("53cr3t");
    

    passwordEncoder方法传递的是PasswordEncoder接口的实现,其默认提供了一些实现,如果都不满足,你可以实现这个接口:

    • BCryptPasswordEncoder

    • NoOpPasswordEncoder

    • Pbkdf2PasswordEncoder

    • SCryptPasswordEncoder

    • StandardPasswordEncoder(SHA-256)

    3.基于LDAP

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.ldapAuthentication()
            .userSearchBase("ou=people")
            .userSearchFilter("(uid={0})")
            .groupSearchBase("ou=groups")
            .groupSearchFilter("member={0}")
            .passwordCompare()
            .passwordEncoder(new BCryptPasswordEncoder())
            .passwordAttribute("passcode")
            .contextSource()
            	.root("dc=tacocloud,dc=com")
            	.ldif("classpath:users.ldif");
    

    4.用户自定义方式(最常用)

    首先,你需要一个用户实体类,它实现UserDetails 接口,实现这个接口的目的是为框架提供更多的信息,你可以把它看作框架使用的实体类:

    @Data
    public class User implements UserDetails {
    
        private Long id;
        private String username;
        private String password;
        private String fullname;
        private String city;
        private String phoneNumber;
        
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return null;
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return false;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return false;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return false;
        }
    
        @Override
        public boolean isEnabled() {
            return false;
        }
    }
    

    有了实体类,你还需要Service逻辑层,springsecurity提供了UserDetailsService 接口,见名知意,你只要通过loadUserByUsername返回一个UserDetails对象就成,无论是基于文件、基于数据库、还是基于LDAP,剩下的对比判断交个框架完成:

    @Service
    public class UserService implements UserDetailsService {
        
        @Override
        public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
            return null;
        }
        
    }
    

    最后,进行应用:

    @Autowired
    private UserDetailsService userDetailsService;
    
    @Bean
    public PasswordEncoder encoder() {
        return new StandardPasswordEncoder("53cr3t");
    }
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
            .passwordEncoder(encoder());
    }
    

    **配置认证路径**

    知道了如何认证,但现在有几个问题,比如,用户登录页面就不需要认证,可以用configure(HttpSecurity http)对认证路径进行配置:

    @Override
    protected void configure(HttpSecurity http) throws Exception {     
    }
    

    你可以通过这个方法,实现以下功能:

    • 在提供接口服务前,判断请求必须满足某些条件
    • 配置登录页面
    • 允许用户注销登录
    • 跨站点伪造请求防护

    1.保护请求

    @Override
    protected void configure(HttpSecurity http) throws Exception {  
        http.authorizeRequests()
            .antMatchers("/design", "/orders").hasRole("ROLE_USER")
            .antMatchers(“/”, "/**").permitAll();
    }
    

    要注意其顺序,除了hasRolepermitAll还有其它访问认证方法:

    方法 作用
    access(String) 如果给定的SpEL表达式的计算结果为true,则允许访问
    anonymous() 允许访问匿名用户
    authenticated() 允许访问经过身份验证的用户
    denyAll() 无条件拒绝访问
    fullyAuthenticated() 如果用户完全通过身份验证,则允许访问
    hasAnyAuthority(String...) 如果用户具有任何给定权限,则允许访问
    hasAnyRole(String...) 如果用户具有任何给定角色,则允许访问
    hasAuthority(String) 如果用户具有给定权限,则允许访问
    hasIpAddress(String) 如果请求来自给定的IP地址,则允许访问
    hasRole(String) 如果用户具有给定角色,则允许访问
    not() 否定任何其他访问方法的影响
    permitAll() 允许无条件访问
    rememberMe() 允许通过remember-me进行身份验证的用户访问

    大部分方法是为特定方式准备的,但是access(String)可以使用SpEL进一些特殊的设置,但其中很大一部分也和上面的方法相同:

    表达式 作用
    authentication 用户的身份验证对象
    denyAll 始终评估为false
    hasAnyRole(list of roles) 如果用户具有任何给定角色,则为true
    hasRole(role) 如果用户具有给定角色,则为true
    hasIpAddress(IP address) 如果请求来自给定的IP地址,则为true
    isAnonymous() 如果用户是匿名用户,则为true
    isAuthenticated() 如果用户已通过身份验证,则为true
    isFullyAuthenticated() 如果用户已完全通过身份验证,则为true(未通过remember-me进行身份验证)
    isRememberMe() 如果用户通过remember-me进行身份验证,则为true
    permitAll 始终评估为true
    principal 用户的主要对象

    示例:

    @Override
    protected void configure(HttpSecurity http) throws Exception {  
        http.authorizeRequests()
            .antMatchers("/design", "/orders").access("hasRole('ROLE_USER')")
            .antMatchers(“/”, "/**").access("permitAll");
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/design", "/orders").access("hasRole('ROLE_USER') && " +
             "T(java.util.Calendar).getInstance().get("+"T(java.util.Calendar).DAY_OF_WEEK) == " +          "T(java.util.Calendar).TUESDAY")
            .antMatchers(“/”, "/**").access("permitAll");
    }
    

    2.配置登录页面

    @Override
    protected void configure(HttpSecurity http) throws Exception {  
        http.authorizeRequests()
            .antMatchers("/design", "/orders").access("hasRole('ROLE_USER')")
            .antMatchers(“/”, "/**").access("permitAll")
            .and()
            .formLogin()
            	.loginPage("/login");
    }
    
    // 增加视图处理器
    @Overridepublic void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("home"); 
        registry.addViewController("/login");
    }
    

    默认情况下,希望传递的是usernamepassword,当然你可以修改:

    .and()
        .formLogin()
        	.loginPage("/login")
        	.loginProcessingUrl("/authenticate")
        	.usernameParameter("user")
        	.passwordParameter("pwd")
    

    也可修改默认登录成功的页面:

    .and()
        .formLogin()
        	.loginPage("/login")
        	.defaultSuccessUrl("/design")
    

    3.配置登出

    .and()
        .logout()
        	 .logoutSuccessUrl("/")
    

    4.csrf攻击

    springsecurity默认开启了防止csrf攻击,你只需要在传递的时候加上:

    <input type="hidden" name="_csrf" th:value="${_csrf.token}"/>
    

    当然,你也可以关闭,但是不建议这样做:

    .and()
        .csrf()
        	.disable()
    

    知道用户是谁

    仅仅控制用户登录有时候是不够的,你可能还想在程序的其它地方获取已经登录的用户信息,有几种方式可以做到:

    • Principal对象注入控制器方法

    • Authentication对象注入控制器方法

    • 使用SecurityContextHolder获取安全上下文

    • 使用@AuthenticationPrincipal注解方法

    1.将Principal对象注入控制器方法

    @PostMappingpublic String processOrder(@Valid Order order, Errors errors,SessionStatus sessionStatus,Principal principal) {
        ...  
        User user = userRepository.findByUsername(principal.getName());
        order.setUser(user);
        ...
    }
    

    2.将Authentication对象注入控制器方法

    @PostMappingpublic String processOrder(@Valid Order order, Errors errors, SessionStatus sessionStatus, Authentication authentication) {
        ...
        User user = (User) authentication.getPrincipal();
        order.setUser(user);
        ...
    }
    

    3.使用SecurityContextHolder获取安全上下文

    Authentication authentication =
        SecurityContextHolder.getContext().getAuthentication();
    	User user = (User) authentication.getPrincipal();
    

    4.使用@AuthenticationPrincipal注解方法

    @PostMappingpublic String processOrder(@Valid Order order, Errors errors,SessionStatus sessionStatus, @AuthenticationPrincipal User user) {
        if (errors.hasErrors()) {
            return "orderForm";  
        }  
        order.setUser(user);
        orderRepo.save(order);
        sessionStatus.setComplete();
        return "redirect:/";
    }
    
  • 相关阅读:
    作业
    第四次作业
    第三次作业
    作业
    第4次作业
    第3次作业,c语言
    第二次作业
    黄义方 作业4
    课堂作业
    第三次作业
  • 原文地址:https://www.cnblogs.com/NameZZH/p/11457146.html
Copyright © 2011-2022 走看看