简单且实用的spring security认证和授权(国庆day3)

    科技2022-08-14  97

    1 认证

    Spring Security认证分为两种:表单认证(如用户名、密码),HTTP基础认证,就是把账号信息放到请求头。

    1.1 认证过程

    认证过程如下图,我们可以按照下图来编写认证的代码,构建Authentication是框架帮我做的,我们接着往下写代码。

    1.2 表单认证

    1.2.1 配置AuthenticationManager

    public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired SysUserRepository sysUserRepository; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(new CusotmUserDetailsService(sysUserRepository)); } }

    1.2.2 编写UserDetailsService

    UserDetailsService依赖于UserDetailsRepository,UserDetailsResposity依赖于SysUser对象。 用户的实体:

    @Data @AllArgsConstructor @NoArgsConstructor @Entity public class SysUser implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String realName; @Column(unique = true) private String username; private String password; private String role; public SysUser(String realName, String username, String password, String role) { this.realName = realName; this.username = username; this.password = password; this.role = role; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return null; } @Override public String getPassword() { return this.password; } @Override public String getUsername() { return this.username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }

    用户的Repository:

    public interface SysUserRepository extends JpaRepository<SysUser, Long> { Optional<SysUser> findByUsername(String username); }

    UserDetailsService:

    public class CusotmUserDetailsService implements UserDetailsService { SysUserRepository sysUserRepository; public CusotmUserDetailsService(SysUserRepository sysUserRepository) { this.sysUserRepository = sysUserRepository; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { Optional<SysUser> sysUserOptional = sysUserRepository.findByUsername(username); return sysUserOptional .orElseThrow(() -> new UsernameNotFoundException("Username not found")); }

    1.2.3 编写控制器测试

    @GetMapping("/") public String hello(){ return "Hello Spring Security"; }

    1.3 HTTP基础认证

    HTTP基础认证过程和表单认证过程机器相似,唯一修改的地方就是添加一个新的HTTP基础认证的AuthenticationManager。

    @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/everyCanAccess").permitAll() .and() .httpBasic().authenticationEntryPoint(authenticationEntryPoint()); }

    在请求头上添加账户信息,信息为Basic+用户名:密码的Base64编码

    1.4 密码编码

    在webSecurityConfig添加编码容器

    @Bean PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); }

    2 授权

    授权分为两种:用户角色授权和方法授权

    2.1 授权过程

    认证完成之后进行授权,授权需要从数据库中查询当前认证用户所属的角色,然后根据角色的权限,判断该用户是否有权限访问资源。

    2.2 用户授权

    2.2.1 配置Web路径的安全访问

    @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired SysUserRepository sysUserRepository; /* @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(new CusotmUserDetailsService(sysUserRepository)); }*/ @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/everyCanAccess").permitAll() .antMatchers("/authenticatedCanAccess").authenticated() .antMatchers("/adminCanAccess").hasRole("ADMIN") .antMatchers("/userCanAccess").access("hasRole('USER') or hasRole('ADMIN')") .antMatchers("/threeCanAccess").access("@webSecurity.checkUsernameLenEq3(authentication)") .anyRequest().authenticated() .and() .httpBasic().authenticationEntryPoint(authenticationEntryPoint()); } @Bean UserDetailsService userDetailsService(SysUserRepository sysUserRepository){ return new CusotmUserDetailsService(sysUserRepository); } @Bean AuthenticationEntryPoint authenticationEntryPoint(){ BasicAuthenticationEntryPoint authenticationEntryPoint = new BasicAuthenticationEntryPoint(); authenticationEntryPoint.setRealmName("wisely"); return authenticationEntryPoint; } @Bean PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }

    配置属性总共包括以下几种:

    access(String) 如果给定的SpEL表达式计算结果为true,就允许访问anonymous() 允许匿名用户访问authenticated() 允许认证的用户进行访问denyAll() 无条件拒绝所有访问fullyAuthenticated() 如果用户是完整认证的话(不是通过Remember-me功能认证的),就允许访问hasAuthority(String) 如果用户具备给定权限的话就允许访问hasAnyAuthority(String…)如果用户具备给定权限中的某一个的话,就允许访问hasRole(String) 如果用户具备给定角色(用户组)的话,就允许访问hasAnyRole(String…) 如果用户具有给定角色(用户组)中的一个的话,允许访问.hasIpAddress(String 如果请求来自给定ip地址的话,就允许访问.not() 对其他访问结果求反.permitAll() 无条件允许访问rememberMe() 如果用户是通过Remember-me功能认证的,就允许访问

    2.2.2 重写SysUser

    重写UserDetails的getAuthorities方法或缺的用户角色

    @Data @AllArgsConstructor @NoArgsConstructor @Entity public class SysUser implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String realName; @Column(unique = true) private String username; private String password; private String role; public SysUser(String realName, String username, String password, String role) { this.realName = realName; this.username = username; this.password = password; this.role = role; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { Collection<SimpleGrantedAuthority> authorities = new ArrayList<>(); SimpleGrantedAuthority authority = new SimpleGrantedAuthority(this.role); authorities.add(authority); return authorities; } @Override public String getPassword() { return this.password; } @Override public String getUsername() { return this.username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }

    2.2.3 编写控制器

    @RestController public class IndexController { @GetMapping("/") public String hello(){ return "Hello Spring Security"; } @GetMapping("/user") public Map<String, Object> getUserInfo(@AuthenticationPrincipal SysUser sysUser, @CurrentSecurityContext SecurityContext securityContext, @CurrentSecurityContext(expression = "authentication") Authentication authentication ){ SecurityContext context = SecurityContextHolder.getContext(); Authentication auth = context.getAuthentication(); Object principal = auth.getPrincipal(); Object details = auth.getDetails(); Map<String, Object> map = new HashMap<>(); map.put("sysUser", sysUser); map.put("authentication", authentication); map.put("principal", principal); map.put("details", details); return map; } @GetMapping("/everyCanAccess") public String everyCanAccess(){ return "任何用户可访问"; } @GetMapping("/authenticatedCanAccess") public String authenticatedCanAccess(){ return "任何登录用户可访问"; } @GetMapping("/userCanAccess") public String userCanAccess(){ return "角色为ROLE_USER或ROLE_ADMIN的用户都可访问"; } @GetMapping("/adminCanAccess") public String adminCanAccess(){ return "角色为ROLE_ADMIN用户可访问"; } @GetMapping("/threeCanAccess") public String threeCanAccess(){ return "只有用户名字符串长度为3的用户可以访问"; } }

    测试:

    2.3 方法授权

    2.3.1 开启方法授权

    @EnableGlobalMethodSecurity(prePostEnabled = true)

    2.3.2 方法授权的不同注解

    @PreAuthorize --适合进入方法之前验证授权@PostAuthorize --检查授权方法之后才被执行@PostFilter --在方法执行之后执行,而且这里可以调用方法的返回值,然后对返回值进行过滤或处理或修改并返回@PreFilter --在方法执行之前执行,而且这里可以调用方法的参数,然后对参数值进行过滤或处理或修改

    2.3.3 给方法授权

    @RestController public class IndexController { @GetMapping("/") public String hello(){ return "Hello Spring Security"; } @GetMapping("/user") public Map<String, Object> getUserInfo(@AuthenticationPrincipal SysUser sysUser, @CurrentSecurityContext SecurityContext securityContext, @CurrentSecurityContext(expression = "authentication") Authentication authentication ){ SecurityContext context = SecurityContextHolder.getContext(); Authentication auth = context.getAuthentication(); Object principal = auth.getPrincipal(); Object details = auth.getDetails(); Map<String, Object> map = new HashMap<>(); map.put("sysUser", sysUser); map.put("authentication", authentication); map.put("principal", principal); map.put("details", details); return map; } @GetMapping("/everyCanAccess") public String everyCanAccess(){ return "任何用户可访问"; } @GetMapping("/authenticatedCanAccess") public String authenticatedCanAccess(){ return "任何登录用户可访问"; } @GetMapping("/userCanAccess") public String userCanAccess(){ return "角色为ROLE_USER或ROLE_ADMIN的用户都可访问"; } @GetMapping("/adminCanAccess") public String adminCanAccess(){ return "角色为ROLE_ADMIN用户可访问"; } @GetMapping("/threeCanAccess") public String threeCanAccess(){ return "只有用户名字符串长度为3的用户可以访问"; } @GetMapping("/methodAdmin") @PreAuthorize("hasRole('ADMIN')") public String methodAdmin(){ return "只有角色为ROLE_ADMIN的用户可访问"; } @GetMapping("/methodDiffName") @PreAuthorize("#user.username != authentication.name") public String methodDiffName(@RequestBody SysUser user){ return "传输的用户名和当前用户名不相同的可访问"; } @GetMapping("/methodNameThree") @PreAuthorize("@webSecurity.checkUsernameLenEq3(authentication)") public String methodNameThree(){ return "只有用户名字符串长度为3的用户可以访问"; } @GetMapping("/methodAnotherName3") @PostAuthorize("returnObject.length() == 5") public String anotherTree(@AuthenticationPrincipal SysUser sysUser){ return "Hi" + sysUser.getUsername(); } @GetMapping("/methodFilterIn") @PreAuthorize("hasRole('USER')") @PreFilter("filterObject%2 == 0") public List<Integer> methodFilterIn (@RequestParam List<Integer> numbers){ return numbers; } @GetMapping("/methodFilterOut") @PostFilter("hasRole('USER') and filterObject%2 == 0") public Integer[] methodFilterIn (){ Integer[] numbers = {1,2,3,4,5,6,7,8,9}; return numbers; } }

    Processed: 0.018, SQL: 8