当前位置:   article > 正文

java使用spirng框架之spring security_adminsecurityconfiguration

adminsecurityconfiguration

spring security 核心功能

  • 认证
  • 授权
  • 攻击防护

使用

引入pom依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-security</artifactId>
  4. </dependency>

有两种授权认证方式

  1. 基于内存
  2. 基于数据库

一、基于内存的认证

1、创建类继承WebSecurityConfigurerAdapter重写两个configure方法

一个是配置AuthenticationManagerBuilder(添加用户),另一个配置HttpSecurity

  1. /**
  2. * 多Security安全配置
  3. */
  4. @Configuration
  5. public class MuitSecurityConfiguration{
  6. /**
  7. * 定义默认的加密方式为不加密
  8. * @return
  9. */
  10. @Bean
  11. PasswordEncoder passwordEncoder(){
  12. return NoOpPasswordEncoder.getInstance();
  13. }
  14. /**
  15. *管理员安全配置
  16. */
  17. @Configuration
  18. @Order(1)//数字越小优先级高,没有order注解,优先级最低
  19. class AdminSecurityConfiguration extends WebSecurityConfigurerAdapter {
  20. @Override
  21. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  22. //创建一个用户admin,并赋予md5密码和admin角色(角色前面需要加ROLE_)
  23. User user=new User("admin","202cb962ac59075b964b07152d234b70", Arrays.asList(new SimpleGrantedAuthority("ROLE_admin")));
  24. //使用基于内存认证方式
  25. //第一种 使用UserDetails
  26. auth.inMemoryAuthentication()
  27. .withUser(user).passwordEncoder(new MessageDigestPasswordEncoder("MD5"));//添加一个用户admin,并设置密码加密方式md5
  28. }
  29. @Override
  30. protected void configure(HttpSecurity http) throws Exception {
  31. http.csrf().disable()//禁用csrf跨站脚本攻击防御
  32. .formLogin()//登录
  33. //.loginPage("/login.html")//登录页面地址(未登录重定向的路径)
  34. .loginProcessingUrl("/login")//登录接口(处理认证请求的路径,表单form中action的地址
  35. .usernameParameter("username")//登录接口参数名username
  36. .passwordParameter("password")//登录接口参数名password
  37. .defaultSuccessUrl("/index.html")//登录成功后的页面
  38. .successHandler(new MyAuthenticationSuccessHandler())//登录成功后处理逻辑
  39. .failureHandler(new MyAuthenticationFailureHandler())//登录失败后处理逻辑
  40. .permitAll()//不需要通过验证就能访问
  41. .and()
  42. .authorizeRequests()//请求验证
  43. .antMatchers("/**.css","/**.js").permitAll()//css文件和js文件都运行访问
  44. .antMatchers("/user/**").hasAnyRole("admin","user")//有admin或user的角色才能访问/user前缀的url
  45. .antMatchers("/db/**").hasAnyAuthority("db")//有访问db资源权限的用户才能访问/user前缀的url
  46. .anyRequest().authenticated()//其他的请求都需要登录后才能访问
  47. .and()
  48. .logout()//退出登录
  49. .logoutUrl("/logout")//退出登录请求的url
  50. .clearAuthentication(true)//清除身份认证信息
  51. .invalidateHttpSession(true)//清除session
  52. .logoutSuccessHandler(new MySimpleUrlLogoutSuccessHandler());//退出登录后处理逻辑
  53. }
  54. }
  55. /**
  56. *普通用户单独安全配置
  57. */
  58. @Configuration
  59. public class OtherSecurityConfiguration extends WebSecurityConfigurerAdapter {
  60. @Override
  61. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  62. //第二种 使用UserDetailsBuilder和上面Bean的空密码加密方式
  63. auth.inMemoryAuthentication()
  64. .withUser("user").password("123").roles("user")//添加一个用户user,密码123,并赋予user角色
  65. .and()
  66. .withUser("db").password("123").authorities("db");//添加一个用户db,密码123,并赋予访问db资源的权限
  67. }
  68. @Override
  69. protected void configure(HttpSecurity http) throws Exception {
  70. http.authorizeRequests()//请求验证
  71. .antMatchers("/**.css","/**.js").permitAll()//css文件和js文件都运行访问
  72. .antMatchers("/admin/**").hasRole("admin")//有admin的角色才能访问/admin前缀的url
  73. .anyRequest().authenticated();//其他的请求都需要登录后才能访问
  74. }
  75. }
  76. }

2、创建自定义处理类:

  1. /**
  2. * 自定义登录成功处理类
  3. */
  4. class MyAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler{
  5. public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
  6. System.out.println("自定义的成功处理逻辑");
  7. //第一种 重定向到defaultSuccessUrl成功页面(适合传统项目)
  8. //super.onAuthenticationSuccess(request, response, authentication);
  9. //第二种 或是返回json串登录成功(适合前后端分离项目)
  10. response.setContentType("text/html;charset=utf-8");
  11. PrintWriter writer = response.getWriter();
  12. HashMap<String, Object> map = new HashMap<>();
  13. map.put("code",0);
  14. map.put("message","登录成功");
  15. writer.write(new ObjectMapper().writeValueAsString(map));
  16. writer.flush();
  17. writer.close();
  18. }
  19. }
  20. /**
  21. * 自定义登录失败处理类
  22. */
  23. class MyAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
  24. @Override
  25. public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
  26. System.out.println("自定义的失败处理逻辑");
  27. //第一种 重定向到失败页面(适合传统项目)
  28. //super.onAuthenticationFailure(request, response, exception);
  29. //第二种 或是返回json串登录失败及原因(适合前后端分离项目)
  30. response.setContentType("text/html;charset=utf-8");
  31. PrintWriter writer = response.getWriter();
  32. response.setStatus(401);
  33. HashMap<String, Object> map = new HashMap<>();
  34. map.put("code",401);
  35. if(exception instanceof LockedException){
  36. map.put("message","账户已锁定,登录失败");
  37. }else if(exception instanceof BadCredentialsException){
  38. map.put("message","用户名或密码错误,登录失败");
  39. }else if(exception instanceof DisabledException){
  40. map.put("message","账户已禁用,登录失败");
  41. }else if(exception instanceof AccountExpiredException){
  42. map.put("message","账户已过期,登录失败");
  43. }else if(exception instanceof CredentialsExpiredException){
  44. map.put("message","密码已过期,登录失败");
  45. }else{
  46. map.put("message","登录失败");
  47. }
  48. writer.write(new ObjectMapper().writeValueAsString(map));
  49. writer.flush();
  50. writer.close();
  51. }
  52. }
  53. /**
  54. * 自定义退出登录成功处理类
  55. */
  56. class MySimpleUrlLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
  57. @Override
  58. public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
  59. System.out.println("自定义的退出登录处理逻辑");
  60. //第一种 重定向到退出成功的页面(适合传统项目)
  61. //super.onLogoutSuccess(request, response, authentication);
  62. //第二种 或是返回json串登录成功(适合前后端分离项目)
  63. response.setContentType("text/html;charset=utf-8");
  64. PrintWriter writer = response.getWriter();
  65. HashMap<String, Object> map = new HashMap<>();
  66. map.put("code",0);
  67. map.put("message","退出登录成功");
  68. writer.write(new ObjectMapper().writeValueAsString(map));
  69. writer.flush();
  70. writer.close();
  71. }
  72. }

3、创建controller接口测试

  1. @RequestMapping("/admin/get")
  2. public String test2() {
  3. return "hello admin";
  4. }
  5. @RequestMapping("/user/get")
  6. public String test3() {
  7. return "hello user";
  8. }
  9. @RequestMapping("/db/get")
  10. public String test4() {
  11. return "hello db";
  12. }

二、基于数据库的认证方式

1、创建user实体,实现UserDetails接口(也可以不用实现,只是第二步会有一点改变)

  1. @Entity
  2. @Table(name = "my_user")
  3. public class User implements UserDetails {
  4. @Id
  5. @GeneratedValue(strategy = GenerationType.AUTO)
  6. @SequenceGenerator(name = "User_SEQ")
  7. @Column(name = "ID")
  8. private Long id;
  9. @Column(name = "username")
  10. private String username;//用户名
  11. @Column(name = "password")
  12. private String password;//密码
  13. @Transient
  14. private Set<GrantedAuthority> authorities;//角色
  15. @Column(name = "accountNonExpired")
  16. private Boolean accountNonExpired;//账户没有过期
  17. @Column(name = "accountNonLocked")
  18. private Boolean accountNonLocked;//账户没有锁定
  19. @Column(name = "credentialsNonExpired")
  20. private Boolean credentialsNonExpired;//密码没有过期
  21. @Column(name = "enabled")
  22. private Boolean enabled;//账户可用
  23. public Long getId() {
  24. return id;
  25. }
  26. public void setId(Long id) {
  27. this.id = id;
  28. }
  29. public void setPassword(String password) {
  30. this.password = password;
  31. }
  32. @Override
  33. public Collection<? extends GrantedAuthority> getAuthorities() {
  34. return authorities;
  35. }
  36. @Override
  37. public String getPassword() {
  38. return password;
  39. }
  40. @Override
  41. public String getUsername() {
  42. return username;
  43. }
  44. public void setUsername(String username) {
  45. this.username = username;
  46. }
  47. public void setAuthorities(Set<GrantedAuthority> authorities) {
  48. this.authorities = authorities;
  49. }
  50. public Boolean getAccountNonExpired() {
  51. return accountNonExpired;
  52. }
  53. public void setAccountNonExpired(Boolean accountNonExpired) {
  54. this.accountNonExpired = accountNonExpired;
  55. }
  56. public Boolean getAccountNonLocked() {
  57. return accountNonLocked;
  58. }
  59. public void setAccountNonLocked(Boolean accountNonLocked) {
  60. this.accountNonLocked = accountNonLocked;
  61. }
  62. public Boolean getCredentialsNonExpired() {
  63. return credentialsNonExpired;
  64. }
  65. public void setCredentialsNonExpired(Boolean credentialsNonExpired) {
  66. this.credentialsNonExpired = credentialsNonExpired;
  67. }
  68. public Boolean getEnabled() {
  69. return enabled;
  70. }
  71. public void setEnabled(Boolean enabled) {
  72. this.enabled = enabled;
  73. }
  74. @Override
  75. public boolean isAccountNonExpired() {
  76. return accountNonExpired;
  77. }
  78. @Override
  79. public boolean isAccountNonLocked() {
  80. return accountNonLocked;
  81. }
  82. @Override
  83. public boolean isCredentialsNonExpired() {
  84. return credentialsNonExpired;
  85. }
  86. @Override
  87. public boolean isEnabled() {
  88. return enabled;
  89. }
  90. }

 2、添加repository写一个根据username查询用户的sql,以下是heirbnate的写法,也可用mybatis

  1. public interface UserRepository extends CrudRepository<User, Long> {
  2. User findByUsername(String username);//根据用户名查询
  3. }

3、添加service接口,继承UserDetailsService。或者serviceImpl实现UserDetailsService也行

  1. public interface UserService extends UserDetailsService {
  2. }

4、 添加serviceImpl实现service。重写loadUserByUsername方法

  1. @Service
  2. public class UserServiceImpl implements UserService {
  3. @Autowired
  4. UserRepository userRepository;
  5. @Override
  6. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  7. User user = userRepository.findByUsername(username);
  8. if(user==null){
  9. throw new UsernameNotFoundException("登录失败,用户名不存在!");
  10. }
  11. return user;
  12. //如果用户没有实现UserDetails接口,则需要拼装org.springframework.security.core.userdetails.User类(UserDetails的实现类)再返回
  13. //例如:
  14. /*
  15. User user=userRepository.findByUsername(username);
  16. org.springframework.security.core.userdetails.User user2=new org.springframework.security.core.userdetails.User(user.getUsername(),user.getPassword(), Arrays.asList(new SimpleGrantedAuthority("ROLE_admin")));
  17. return user2;
  18. * */
  19. }
  20. }

 用户登录时,security会调用loadUserByUsername方法去数据库查询数据

5、添加配置。auth.userDetailsService使用基于数据库的认证方式

  1. @Configuration
  2. public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
  3. @Autowired
  4. UserService userService;
  5. @Bean
  6. PasswordEncoder passwordEncoder(){
  7. return NoOpPasswordEncoder.getInstance();
  8. }
  9. @Override
  10. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  11. auth.userDetailsService(userService);
  12. }
  13. @Override
  14. protected void configure(HttpSecurity http) throws Exception {
  15. //……胜率代码
  16. }
  17. }

6、添加数据库用户数据,实验登录

 

7、完毕

 

三、基于注解的拦截方式

以上都是基于url的拦截方式

还有一种基于注解的拦截方式,用于保护方法

1、添加@EnableGlobalMethodSecurity注解,prePostEnabled ,securedEnabled设置为true

  1. @SpringBootApplication
  2. //打开Security方法安全注解,可以使用@PreAuthorize、@PostAuthorize和@Secured注解
  3. @EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
  4. public class DemoApplication {
  5. public static void main(String[] args) {
  6. SpringApplication.run(DemoApplication.class, args);
  7. }
  8. }

2、在controller方法中添加 @PreAuthorize、@PostAuthorize、@Secured注解

  1. @RequestMapping("/test")
  2. @PreAuthorize("hasRole('normal') and hasRole('normal3')")//有normal和normal3角色才能访问此接口
  3. public String test() {
  4. return "hello normal and normal3";
  5. }
  6. @RequestMapping("/testt")
  7. @PostAuthorize("hasAnyRole('admin','normal2')")//有normal1或normal2角色才能访问此接口
  8. public String testt() {
  9. return "hello admin or normal2";
  10. }
  11. @RequestMapping("/testtt")
  12. @Secured("normal3")//有normal3角色才能访问此接口
  13. public String testtt() {
  14. return "hello normal3";
  15. }

四、角色继承关系

     * 例如:admin角色同时具有db角色和user角色的权限
     * db角色同时具有user角色的权限

  1. /**
  2. * 角色继承关系
  3. * 例如:admin角色同时具有db角色和user角色的权限
  4. * db角色同时具有user角色的权限
  5. * @return
  6. */
  7. @Bean
  8. RoleHierarchy roleHierarchy(){
  9. RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
  10. roleHierarchy.setHierarchy("ROLE_admin > ROLE_db ROLE_db > ROLE_user");
  11. return roleHierarchy;
  12. }

五、动态权限配置

以上权限都是写死在代码里,不够灵活,无法实现资源和角色的动态跳转

除此之外还可以自定义权限配置(记录在数据库表中),实现动态url权限

一、增加四个表

1、用户表sys_user (id、username、password)

对应数据库表增加一条测试数据admin

2、角色表sys_role (id、name)

对应数据库表增加一条测试数据admin

3、权限表sys_authority (id、code、url_pattern)

对应数据库表增加两条权限

4、用户角色权限关联表sys_user_role_authority  (id、user_id、role_id、authority_id)

对应数据库表增加两条关联数据 admin拥有admin角色和user角色

二、添加repository写一些查询

  1. public interface UserRepository extends CrudRepository<User, Long> {
  2. /**
  3. * 根据用户名查询用户
  4. * @param username
  5. * @return
  6. */
  7. User findByUsername(String username);
  8. /**
  9. * 根据用户名查询用户拥有的权限
  10. * @param username
  11. * @return
  12. */
  13. @Query("select u.authority from UserRoleAuthority u where u.user.username=?1 ")
  14. List<Authority> findAuthorityByUsername(String username);
  15. /**
  16. * 查询所有的权限
  17. * @return
  18. */
  19. @Query("from Authority")
  20. List<Authority> findAllAuthority();
  21. }

三、自定义类实现FilterInvocationSecurityMetadataSource接口的getAttributes、getAllConfigAttributes和supports三个方法

  1. /**
  2. * 自定义认证规则,
  3. * 也就是用于加载URL与权限对应关系的
  4. */
  5. @Configuration
  6. class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
  7. /**
  8. * 定义成员变量spring的AntPathMatcher用来匹配url(构造方法设置分隔符)
  9. */
  10. AntPathMatcher pathMatcher = new AntPathMatcher(AntPathMatcher.DEFAULT_PATH_SEPARATOR);
  11. /**
  12. * 获取某个受保护的安全对象object的所需要的权限信息,是一组ConfigAttribute对象的集合,
  13. * 如果该安全对象object不被当前SecurityMetadataSource对象支持,则抛出异常IllegalArgumentException。
  14. * 与supports配合使用
  15. */
  16. @Override
  17. public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
  18. String requestUrl = ((FilterInvocation) object).getRequestUrl();//获取用户请求的路径
  19. //应使用数据库查询角色和拦截路径的关系
  20. //查询出所有的url和和权限
  21. List<Authority> list = userRepository.findAllAuthority();
  22. List<ConfigAttribute> matchedList = new ArrayList<>();
  23. for (Authority authority : list) {
  24. if(pathMatcher.match(authority.getUrl_pattern(),requestUrl)){//进行url匹配规则校验
  25. matchedList.add(new SecurityConfig(authority.getCode()));
  26. }
  27. }
  28. if(!matchedList.isEmpty()){//如果规则能够匹配
  29. return matchedList;
  30. }
  31. return SecurityConfig.createList("ROLE_LOGIN");
  32. }
  33. /**
  34. * 获取该SecurityMetadataSource对象中保存的针对所有安全对象的权限信息的集合。
  35. * 该方法的主要目的是被AbstractSecurityInterceptor用于启动时校验每个ConfigAttribute对象。
  36. * @return
  37. */
  38. @Override
  39. public Collection<ConfigAttribute> getAllConfigAttributes() {
  40. return null;
  41. }
  42. /**
  43. * 用于告知调用者当前SecurityMetadataSource是否支持此类安全对象,只有支持的时候,才能对这类安全对象调用getAttributes方法
  44. * @param clazz
  45. * @return
  46. */
  47. @Override
  48. public boolean supports(Class<?> clazz) {
  49. return true;
  50. }
  51. }

四、自定义类实现AccessDecisionManager接口的decide和supports方法

  1. /**
  2. * 自定义的决策管理器,
  3. * 用来决定对于一个用户的请求是否基于通过的中心控制
  4. */
  5. class MyAccessDecisionManager implements AccessDecisionManager{
  6. /**
  7. * 对比决策.如果当前用户允许登录,那么直接return即可。
  8. * 如果当前用户不许运行登录,则抛出一个 AccessDeniedException异常。
  9. * @param authentication 当前登录用户信息
  10. * @param configAttributes SecurityMetadataSource.getAttributes() 方法获取这个URL相关的权限
  11. */
  12. @Override
  13. public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
  14. //非空判断
  15. if(configAttributes.isEmpty()){
  16. return;
  17. }
  18. //获取访问此url所需要的权限集合
  19. Iterator<ConfigAttribute> iterator = configAttributes.iterator();
  20. while (iterator.hasNext()){
  21. ConfigAttribute configAttribute = iterator.next();
  22. //获取当前用户的权限和url权限集合进行比较。如果运行则return,否则抛出异常
  23. for (GrantedAuthority authority : authentication.getAuthorities()) {
  24. if(authority.getAuthority().equals(configAttribute.getAttribute())){
  25. return;
  26. }
  27. }
  28. }
  29. throw new AccessDeniedException("No Authority");
  30. }
  31. @Override
  32. public boolean supports(ConfigAttribute attribute) {
  33. return true;
  34. }
  35. @Override
  36. public boolean supports(Class<?> clazz) {
  37. return true;
  38. }
  39. }

五、增加config配置

  1. @Configuration
  2. public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
  3. @Autowired
  4. UserService userService;
  5. @Autowired
  6. UserRepository userRepository;
  7. @Bean
  8. PasswordEncoder passwordEncoder(){
  9. return NoOpPasswordEncoder.getInstance();
  10. }
  11. @Override
  12. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  13. auth.userDetailsService(userService);
  14. }
  15. @Override
  16. protected void configure(HttpSecurity http) throws Exception {
  17. http.csrf().disable()//禁用csrf跨站脚本攻击防御
  18. .formLogin()//登录
  19. .permitAll()//不需要通过验证就能访问
  20. .and()
  21. .authorizeRequests()//请求验证
  22. .antMatchers("/**.css","/**.js").permitAll()//css文件和js文件都运行访问
  23. .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
  24. @Override
  25. public <O extends FilterSecurityInterceptor> O postProcess(O object) {
  26. object.setAccessDecisionManager(new MyAccessDecisionManager());//注入用户登录权限
  27. object.setSecurityMetadataSource(new MyFilterInvocationSecurityMetadataSource());//注入用户所请求的地址所需要的权限
  28. return object;
  29. }
  30. })
  31. .anyRequest().authenticated()//其他的请求都需要登录后才能访问
  32. .and()
  33. .logout()//退出登录
  34. .logoutUrl("/logout")//退出登录请求的url
  35. .clearAuthentication(true)//清除身份认证信息
  36. .invalidateHttpSession(true);//清除session
  37. }
  38. }

六、登录测试admin

访问/admin/get可以访问

访问/db/get,报错403

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号