SpringBoot安全认证Security的实现方法

一、基本环境搭建

父pom依赖

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.0.3.RELEASE</version>
</parent>

1. 添加pom依赖:

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

2. 创建测试用Controller

@RestController
public class TestController {

  @GetMapping("getData")
  public String getData() {
    return "date";
  }

}

3. 创建SpringBoot启动类并run

@SpringBootApplication
public class SpringBootTestApplication {

  public static void main(String[] args) {
    SpringApplication.run(SpringBootTestApplication.class, args);
  }

}

4. 测试

访问http://127.0.0.1:8080/getData,由于我们开启了SpringSecurity且当前是未登录状态,页面会被302重定向到http://127.0.0.1:8080/login,页面如下:

用户名:user,密码可以在控制台输出中找到:

输入正确的用户名和密码后点击Login按钮即被重新302到http://127.0.0.1:8080/getData并显示查询数据:

这表示我们的接口已经被spring保护了。

那么肯定会有小伙伴吐槽了,这么复杂的密码,鬼才及得住,所以...

二、为Spring Security设定用户名和密码

为了解决复杂密码的问题,我们可以在application.yml中做如下设定:

spring:
 security:
  user:
   name: user
   password: 123

这样我们就可以通过用户名user密码123来访问http://127.0.0.1:8080/getData接口了。

然后肯定又有小伙伴吐槽了,整个系统就一个用户么?有哪个系统是只有一个用户的?所以...

三、为Spring Security设定多个用户

如果想要给Spring Security设定多个用户可用,则新建一个class,实现接口WebMvcConfigurer(注意:springBoot版本2.0以上,jdk1.8以上):

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

  @Bean
  public UserDetailsService userDetailsService() {
    InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
    manager.createUser(User.withUsername("admin").password("admin").roles("").build());
    manager.createUser(User.withUsername("guest").password("guest").roles("").build());
    return manager;
  }

}

注意需要注解@EnableWebSecurity

InMemoryUserDetailsManager:顾名思义,将用户名密码存储在内存中的用户管理器。我们通过这个管理器增加了两个用户,分别是:用户名admin密码admin,用户名guest密码guest。

做完如上更改后重启应用,再次访问http://127.0.0.1:8080/getData,输入admin/admin或guest/guest即可通过身份验证并正常使用接口了。

看到这肯定又有小伙伴要吐槽了:用户数据直接硬编码到代码里是什么鬼!我要把用户放在数据库!所以...

四、SpringSecurity+Mysql

想要使用数据库,那么我们可以

1. 增加如下依赖:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
</dependency>

2. 配置数据库连接

spring:
 datasource:
  driver-class-name: com.mysql.jdbc.Driver
  url: jdbc:mysql://192.168.2.12:3306/test?characterEncoding=utf8
  username: root
  password: onceas

3. 创建测试用表结构及数据

drop table if exists test.user;
create table test.user (
 id int auto_increment primary key,
 username varchar(50),
 password varchar(50)
);

insert into test.user(id, username, password) values (1, 'admin', 'admin');
insert into test.user(id, username, password) values (2, 'guest', 'guest');

我们创建了用户信息表,并插入两个用户信息,用户名/密码依然是admin/admin、guest/guest

4. entity、dao、service

public class User {

  private int id;
  private String username;
  private String password;

  // get set ...
}
@Repository
public class LoginDao {

  private final JdbcTemplate jdbcTemplate;

  @Autowired
  public LoginDao(JdbcTemplate jdbcTemplate) {
    this.jdbcTemplate = jdbcTemplate;
  }

  public List<User> getUserByUsername(String username) {
    String sql = "select id, username, password from user where username = ?";
    return jdbcTemplate.query(sql, new String[]{username}, new BeanPropertyRowMapper<>(User.class));
  }
}
@Service
public class LoginService {

  private final LoginDao loginDao;

  @Autowired
  public LoginService(LoginDao loginDao) {
    this.loginDao = loginDao;
  }

  public List<User> getUserByUsername(String username) {
    return loginDao.getUserByUsername(username);
  }

}

5. 调整WebSecurityConfig

@Bean
public UserDetailsService userDetailsService() {
  return username -> {
    List<UserEntity> users = loginService.getUserByUsername(username);
    if (users == null || users.size() == 0) {
      throw new UsernameNotFoundException("用户名未找到");
    }
    String password = users.get(0).getPassword();
    PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
    String passwordAfterEncoder = passwordEncoder.encode(password);
    return User.withUsername(username).password(passwordAfterEncoder).roles("").build();
  };
}

做完如上更改后重启应用,再次访问http://127.0.0.1:8080/getData,输入admin/admin或guest/guest即可通过身份验证并正常使用接口了。

关于UserDetailsService,有些东西要说明下:

PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
String passwordAfterEncoder = passwordEncoder.encode(password);

上面这两句代码是在对用户密码进行加密。为什么要这样子呢?看到这肯定又有小伙伴会吐槽:数据库存储铭文密码是什么鬼!对,Spring也是尽量在帮助开发者避免这个事情。所以SpringSecurity在进行密码比对的时候需要开发者提供加密后的密码。我们上面的写法其实是不合理的,实际情况应该是数据库中存储密文密码,然后将数据库中的密码直接传给User.password()就可以了。

6. 关于SpringSecurity加密后的密文格式

我们可以通过打断点的方式或者增加

System.out.println(username + "---->>>" + passwordAfterEncoder);

来查看下,如果admin/admin被登录时候,passwordAfterEncoder的值是什么?输出结果:

admin---->>>{bcrypt}$2a$10$d4VkiIfP7MyNSipjLtQ0Keva4ST6U6Fnw77iiv39IGnGswptqWRG.
guest---->>>{bcrypt}$2a$10$8jRMbiGzFIS4GU3SWAm83eWgFO29EEb5QhXOEkPEaabw5Oiy/jxUC

可以看出加密后的密码可以分为两部分

{}内描述了加密算法,这里为bcrypt算法。{}后面即为密文密码,这里是包含盐的。

所以SpringSecurity的工作原理就是:当用户输入用户名和密码点击Login以后,SpringSecurity先通过调用我们自定义的UserDetailsService获取到加密后密码,然后根据{}里的内容获知加密算法,再将用户输入的密码按照该算法进行加密,最后再与{}后的密文密码比对即可获知用户凭据是否有效。

通过查看PasswordEncoderFactories的源码,我们可以知道SpringEncoder工具可以提供哪些加密算法:

public static PasswordEncoder createDelegatingPasswordEncoder() {
    String encodingId = "bcrypt";
    Map<String, PasswordEncoder> encoders = new HashMap<>();
    encoders.put(encodingId, new BCryptPasswordEncoder());
    encoders.put("ldap", new LdapShaPasswordEncoder());
    encoders.put("MD4", new Md4PasswordEncoder());
    encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
    encoders.put("noop", NoOpPasswordEncoder.getInstance());
    encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
    encoders.put("scrypt", new SCryptPasswordEncoder());
    encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));
    encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256"));
    encoders.put("sha256", new StandardPasswordEncoder());
    return new DelegatingPasswordEncoder(encodingId, encoders);
  }

其中LdapShaPasswordEncoder、Md4PasswordEncoder、MessageDigestPasswordEncoder、NoOpPasswordEncoder、StandardPasswordEncoder已经不建议使用了。SpringSecurity认为:

Digest based password encoding is not considered secure. //基于摘要的密码编码被认为是不安全的

五 、权限控制

以上内容我们只解决了用户登录问题,但是实际开发中仅仅完成用户登录是不够的,我们还需要用户授权及授权验证。由于我们已经将用户信息存储到数据库里了,那么姑且我们也将权限信息存储在数据库吧。

1. 准备数据库表及测试数据

drop table if exists test.role;
create table test.role (
 id int auto_increment primary key,
 role varchar(50)
);

drop table if exists test.permission;
create table test.permission (
 id int auto_increment primary key,
 permission varchar(50)
);

drop table if exists test.user_r_role;
create table test.user_r_role (
 userid int,
 roleid int
);

drop table if exists test.role_r_permission;
create table test.role_r_permission (
 roleid int,
 permissionid int
);

drop table if exists test.user_r_permission;
create table test.user_r_permission (
 userid int,
 permissionid int
);

insert into test.role(id, role) values (1, 'adminRole');
insert into test.role(id, role) values (2, 'guestRole');

insert into test.permission(id, permission) values (1, 'permission1');
insert into test.permission(id, permission) values (2, 'permission2');
insert into test.permission(id, permission) values (3, 'permission3');
insert into test.permission(id, permission) values (4, 'permission4');

insert into test.user_r_role(userid, roleid) values (1, 1);
insert into test.user_r_role(userid, roleid) values (2, 2);

insert into test.role_r_permission(roleid, permissionid) values (1, 1);
insert into test.role_r_permission(roleid, permissionid) values (1, 2);

insert into test.user_r_permission(userid, permissionid) values (1, 3);
insert into test.user_r_permission(userid, permissionid) values (1, 4);
insert into test.user_r_permission(userid, permissionid) values (2, 3);
insert into test.user_r_permission(userid, permissionid) values (2, 4);
  1. role:角色信息表,permission权限信息表,user_r_role用户所属角色表,role_r_permission角色拥有权限表,user_r_permission用户拥有权限表。
  2. 由于用户有所属角色且角色是有权限的,用户同时又单独拥有权限,所以用户最终拥有的权限取并集。
  3. 用户admin最终拥有角色adminRole以及权限:permission1、permission2、permission3、permission4
  4. 用户guest最终拥有角色guestRole以及权限:permission3、permission4

2. Dao、Service

dao增加方法:根据用户名查角色以及根据用户名查权限

public List<String> getPermissionsByUsername(String username) {
  String sql =
      "select d.permission\n" +
      "from user a\n" +
      "    join user_r_role b on a.id = b.userid\n" +
      "    join role_r_permission c on b.roleid = c.roleid\n" +
      "    join permission d on c.permissionid = d.id\n" +
      "where a.username = ?\n" +
      "union\n" +
      "select c.permission\n" +
      "from user a\n" +
      "    join user_r_permission b on a.id = b.userid\n" +
      "    join permission c on b.permissionid = c.id\n" +
      "where a.username = ?";
  return jdbcTemplate.queryForList(sql, new String[]{username, username}, String.class);
}

public List<String> getRoleByUsername(String username) {
  String sql =
      "select c.role\n" +
      "from user a\n" +
      "    join user_r_role b on a.id = b.userid\n" +
      "    join role c on b.roleid = c.id\n" +
      "where a.username = ?";
  return jdbcTemplate.queryForList(sql, new String[]{username}, String.class);
}

service增加方法:根据用户名查角色以及根据用户名查权限

public List<String> getPermissionsByUsername(String username) {
  return loginDao.getPermissionsByUsername(username);
}

public List<String> getRoleByUsername(String username) {
  return loginDao.getRoleByUsername(username);
}

3. WebSecurityConfig

(1)调整public UserDetailsService userDetailsService()方法,在构建用户信息的时候把用户所属角色和用户所拥有的权限也填充上(最后return的时候)。

@Bean
public UserDetailsService userDetailsService() {
  return username -> {
    List<UserEntity> users = loginService.getUserByUsername(username);
    if (users == null || users.size() == 0) {
      throw new UsernameNotFoundException("用户名未找到");
    }
    String password = users.get(0).getPassword();
    PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
    String passwordAfterEncoder = passwordEncoder.encode(password);
    System.out.println(username + "/" + passwordAfterEncoder);

    List<String> roles = loginService.getRoleByUsername(username);
    List<String> permissions = loginService.getPermissionsByUsername(username);

    String[] roleArr = new String[roles.size()];
    String[] permissionArr = new String[permissions.size()];

    return User.withUsername(username).password(passwordAfterEncoder).
        roles(roles.toArray(roleArr)).authorities(permissions.toArray(permissionArr)).
        build();
  };
}

这里面有个坑,就是红色代码部分。具体可查看org.springframework.security.core.userdetails.User.UserBuilder。roles()方法和authorities()方法实际上都是在针对UserBuilder的authorities属性进行set操作,执行roles("roleName")和执行authorities("ROLE_roleName")是等价的。所以上例代码中roles(roles.toArray(roleArr))起不到任何作用,直接被后面的authorities(permissions.toArray(permissionArr))覆盖掉了。

所以正确的写法可参考:

@Bean
public UserDetailsService userDetailsService() {
  return username -> {
    List<UserEntity> users = loginService.getUserByUsername(username);
    if (users == null || users.size() == 0) {
      throw new UsernameNotFoundException("用户名未找到");
    }
    String password = users.get(0).getPassword();
    PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
    String passwordAfterEncoder = passwordEncoder.encode(password);
    System.out.println(username + "/" + passwordAfterEncoder);

    List<String> roles = loginService.getRoleByUsername(username);
    List<String> permissions = loginService.getPermissionsByUsername(username);

    String[] permissionArr = new String[roles.size() + permissions.size()];
    int permissionArrIndex = 0;
    for (String role : roles) {
      permissionArr[permissionArrIndex] = "ROLE_" + role;
      permissionArrIndex++;
    }
    for (String permission : permissions) {
      permissionArr[permissionArrIndex] = permission;
      permissionArrIndex++;
    }
    return User.withUsername(username).password(passwordAfterEncoder).authorities(permissionArr).build();
  };
}

(2)增加新的bean,为我们需要的保护的接口设定需要权限验证:

@Bean
public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter() {
  return new WebSecurityConfigurerAdapter() {
    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception {
      httpSecurity.
          authorizeRequests().antMatchers("/guest/**").permitAll().
          and().authorizeRequests().antMatchers("/admin/**").hasRole("admin").
          and().authorizeRequests().antMatchers("/authenticated/**").authenticated().
          and().authorizeRequests().antMatchers("/permission1/**").hasAuthority("permission1").
          and().authorizeRequests().antMatchers("/permission2/**").hasAuthority("permission2").
          and().authorizeRequests().antMatchers("/permission3/**").hasAuthority("permission3").
          and().authorizeRequests().antMatchers("/permission4/**").hasAuthority("permission4").
          and().formLogin().
          and().authorizeRequests().anyRequest().permitAll();
    }
  };
}
  1. /guest/**的接口会被允许所有人访问,包括未登录的人。
  2. /admin/**的接口只能被拥有admin角色的用户访问。
  3. /authenticated/**的接口可以被所有已经登录的用户访问。
  4. /permission1/**的接口可以被拥有permission1权限的用户访问。/permission2/**、/permission3/**、/permission4/**同理

4. TestController

最后我们调整下TestContrller,增加几个接口以便测试:

@RestController
public class TestController {

  @GetMapping("getData")
  public String getData() {
    return "date";
  }

  @GetMapping("authenticated/getData")
  public String getAuthenticatedData() {
    return "authenticatedData";
  }

  @GetMapping("admin/getData")
  public String getAdminData() {
    return "adminData";
  }

  @GetMapping("guest/getData")
  public String getGuestData() {
    return "guestData";
  }

  @GetMapping("permission1/getData")
  public String getPermission1Data() {
    return "permission1Data";
  }

  @GetMapping("permission2/getData")
  public String getPermission2Data() {
    return "permission2Data";
  }

  @GetMapping("permission3/getData")
  public String getPermission3Data() {
    return "permission3Data";
  }

  @GetMapping("permission4/getData")
  public String getPermission4Data() {
    return "permission4Data";
  }

}

5. 测试

  1. 访问/guest/getData无需登录即可访问成功。
  2. 访问/authenticated/getData,会弹出用户登录页面。登录任何一个用户都可访问成功。
  3. 访问/admin/getData,会弹出用户登录页面。登录admin用户访问成功,登录guest用户会发生错误,403未授权。
  4. 其他的就不再赘述了。

六、自定义登录页面

是不是觉得SpringScurity的登录页面丑爆了?是不是想老子还能做一个更丑的登录页面你信不信?接下来我们来弄一个更丑的登录页面。

1. 增加pom依赖

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

2. 编写自己的登录页面

thymeleaf默认的页面放置位置为:classpath:templates/ 目录下,所以在编写代码的时候我们可以将页面放在resources/templates目录下,名称为:login.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>一个更丑的登录页面</title>
</head>
<body>
  <form method="post" action="/login">
    用户名:<input name="username" placeholder="请输入用户名" type="text">
    密码:<input name="password" placeholder="请输入密码" type="password">
    <input value="登录" type="submit">
  </form>
</body>
</html>

3. 将SpringSecurity指向自定义的登录页面

(1)调整WebSecurityConfig注入的WebSecurityConfigurerAdapter,在and().formLogin()后面增加loginPage("/login")以指定登录页面的uri地址,同时关闭csrf安全保护。

@Bean
public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter() {
  return new WebSecurityConfigurerAdapter() {
    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception {
      httpSecurity.
          authorizeRequests().antMatchers("/guest/**").permitAll().
          ...省略部分代码...
          and().formLogin().loginPage("/login").
          and().authorizeRequests().anyRequest().permitAll().
          and().csrf().disable();
    }
  };
}

(2)TestController增加login方法(注意我们之前在TestController类上注解了@RestController,这里要记得改成@Controller,否则访问/login的时候会直接返回字符串而不是返回html页面。另外除了下面新增的/login方法其他方法要增加注解@ResponseBody)

@GetMapping("login")
public String login() {
  return "login";
}

4. 测试及其他

测试过程就略吧。还有一些要嘱咐的东西给小白们:

  • 我们通过loginPage("/login")来告知SpringSecurity自定义登录页面的uri路径,同时这个设定也告知了用户点击登录按钮的时候form表单post的uri路径。即:如果SpringSecurity判定需要用户登录,会将302到/login (get请求),用户输入用户名和密码点击登录按钮后,也需要我们自定义页面post到/login才能让SpringSecurity完成用户认证过程。
  • 关于html中输入用户名的input的name属性值本例为username、输入密码的input的name属性值本例为password,这是因为SpringSecurity在接收用户登录请求时候默认的参数名就是username和password、如果想更改这两个参数名,可以这样设定:and().formLogin().loginPage("/login").usernameParameter("username").passwordParameter("password")
  • 测试过程中我们可以试着输错用户名和密码点击登录,会发现页面又重新跳转到 http://127.0.0.1:8080/login?error ,只不过后面增加了参数error且没有参数值。所以需要我们再login.html中处理相应的逻辑。当然你也可以指定用户认证失败时候的跳转地址,可以这样设定:and().formLogin().loginPage("/login").failureForwardUrl("/login/error")
  • 测试过程中,如果我们直接访问http://127.0.0.1:8080/login,输入正确的用户名和密码后跳转到http://127.0.0.1:8080即网站根目录。如果你想指定用户登录成功后的默认跳转地址,可以这样设定:and().formLogin().loginPage("/login").successForwardUrl("/login/success")

七、登出

登出呢?有登录了,怎么能没有登出呢?其实SpringSecurity已经早早的为我们默认了一个登出功能,你访问:http://127.0.0.1:8080/logout 试试看?

如果想做我们自己的个性化登出,可以继续调整WebSecurityConfig注入的WebSecurityConfigurerAdapter

@Bean
public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter() {
  return new WebSecurityConfigurerAdapter() {
    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception {
      httpSecurity.
          authorizeRequests().antMatchers("/guest/**").permitAll().
          and().authorizeRequests().antMatchers("/admin/**").hasRole("admin").
          and().authorizeRequests().antMatchers("/authenticated/**").authenticated().
          and().authorizeRequests().antMatchers("/permission1/**").hasAuthority("permission1").
          and().authorizeRequests().antMatchers("/permission2/**").hasAuthority("permission2").
          and().authorizeRequests().antMatchers("/permission3/**").hasAuthority("permission3").
          and().authorizeRequests().antMatchers("/permission4/**").hasAuthority("permission4").
          and().formLogin().loginPage("/login").
          and().logout().logoutUrl("/logout").logoutSuccessUrl("/logoutSuccess").
                  invalidateHttpSession(true).deleteCookies("cookiename").
                  addLogoutHandler(new MyLogoutHandle()).logoutSuccessHandler(new MyLogoutSuccessHandle()).
          and().authorizeRequests().anyRequest().permitAll().
          and().csrf().disable();
    }
  };
}

MyLogoutHandle实现了LogoutHandler接口:

public class MyLogoutHandle implements LogoutHandler {

  @Override
  public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) {
    System.out.println("==================>>>> LogoutHandler Begin");
    System.out.println(authentication.getPrincipal());
    System.out.println("==================>>>> LogoutHandler End");
  }
}

•MyLogoutSuccessHandle实现了LogoutSuccessHandler接口:

public class MyLogoutSuccessHandle implements LogoutSuccessHandler {

  @Override
  public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
    System.out.println("==================>>>> LogoutSuccessHandler Begin");
    System.out.println(authentication.getPrincipal());
    System.out.println("==================>>>> LogoutSuccessHandler End");
  }
}
  • logoutUrl():告诉SpringSecurity用户登出的接口uri地址是什么
  • logoutSuccessUrl():告诉SpringSecurity完成用户登出后要跳转到哪个地址。如果设定了LogoutSuccessHandler则logoutSuccessUrl设定无效
  • invalidateHttpSession:执行登出的同时是否清空session
  • deleteCookies:执行登出的同时删除那些cookie
  • addLogoutHandler:执行登出的同时执行那些代码

八、SpringSecurity在Restfull中的变通使用

当前环境前后盾分离已经是大趋势了吧,除非那些很小很小的项目。所以SpringBoot项目更多的时候为前端提供接口,而并不提供前端页面路由的功能。所以,当SpringSecurity在Restfull开发中还需要变通一下:

1.首先我们通过and().formLogin().loginPage("/login")设定的跳转到登录页面的GET请求不再指向html,而是直接返回json数据告知前端需要用户登录。
2.用户执行登录的时候,前端执行post请求到/login进行用户身份校验。
3.然后我们通过and().formLogin().failureForwardUrl("/login/error")和and().formLogin().successForwardUrl("/login/error")设定的登录成功和失败跳转来地址来返回json数据给前端告知其用户认证结果。
4.最后我们通过and().logout().logoutSuccessHandler(new MyLogoutSuccessHandle())来返回json数据给前端告知用户已经完成登出。

九、SpringSecurity+SpringSession+Redis

接下来还有一个问题要处理。在上面的案例中,session都是存储在servlet容器中的,如果我们需要多点部署负载均衡的话,就会出现问题。比如:我们部署了两个服务并做了负载均衡,用户登录时调用其中一台服务进行身份认证通过并将用户登录信息存储在了这台服务器的session里,接下来用户访问其他接口,由于负载均衡的存在用户请求被分配到了另一个服务上,该服务检测用户session不存在啊,于是就拒绝访问。

在SpringBoot环境下解决这个问题也很简答,很容易就想到SpringSession。所以我们尝试用SpringSession+Redis解决此问题

1. 增加pom依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-data-redis</artifactId>
</dependency>

2. 修改application.yml

spring:
 redis:
  host: 192.168.2.12
  port: 6379
  password: 123456

 session:
  store-type: redis

3. 修改主启动类,增加@EnableRedisHttpSession注解,开启SpringSession

十、通过注解的方式实现权限控制

首先要在主启动类上增加@EnableGlobalMethodSecurity注解,具体参数如下:

1.@EnableGlobalMethodSecurity(securedEnabled=true)

支持@Secured注解,例如

@Secured("ROLE_adminRole")

2.@EnableGlobalMethodSecurity(jsr250Enabled=true)

支持@RolesAllowed、@DenyAll、@PermitAll 注解,例如:

@RolesAllowed("ROLE_guestRole")

3.@EnableGlobalMethodSecurity(prePostEnabled=true)

支持@PreAuthorize、@PostAuthorize、@PreFilter、@PostFilter注解,它们使用SpEL能够在方法调用上实现更有意思的安全性约束

  • @PreAuthorize :在方法调用之前,基于表达式的计算结果来限制对方法的访问,只有表达式计算结果为true才允许执行方法
  • @PostAuthorize 在方法调用之后,允许方法调用,但是如果表达式计算结果为false,将抛出一个安全性异常
  • @PostFilter 允许方法调用,但必须按照表达式来过滤方法的结果
  • @PreFilter 允许方法调用,但必须在进入方法之前过滤输入值

由于这里涉及到SpEL表达式,所以本文就不详细说了。

十一、在Controller中获取当前登录用户

public String getAuthenticatedData(HttpSession session) {
    //SecurityContext securityContext = SecurityContextHolder.getContext();
    SecurityContext securityContext = (SecurityContext) session.getAttribute("SPRING_SECURITY_CONTEXT");
    // 以上获取securityContext的两种方法二选一
    WebAuthenticationDetails userDetailsService = (WebAuthenticationDetails) securityContext.getAuthentication().getDetails();
    UserDetails userDetails = (UserDetails) securityContext.getAuthentication().getPrincipal();

    System.out.println("===userDetailsService.getRemoteAddress()===>>" + userDetailsService.getRemoteAddress());
    System.out.println("===userDetailsService.getSessionId()===>>" + userDetailsService.getSessionId());
    System.out.println("===userDetails.getRemoteAddress()===>>" + userDetails.getUsername());
    System.out.println("===userDetails.getPassword()===>>" + userDetails.getPassword());
    System.out.println("===userDetails.getAuthorities()===>>" + userDetails.getAuthorities());
    return "authenticatedData";
  }

十二、总结

SpringSecurity的使用基本就上面这些。就业务逻辑来说,SpringSecurity中所谓的role概念严格意义并不能称之为“角色”。理由是:如果我们的权限控制比较简单,整个系统中的角色以及角色所拥有的权限是固定的,那么我们可以将SpringSecurity的role概念拿来即用。但是如果我们的权限控制是可配置,用户和角色是多对多关系、角色和权限也是多对多关系,那么我们只能讲SpringSecurity的role当做“权限”来使用。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Spring Boot整合Spring Security的示例代码

    本文讲述Spring Boot整合Spring Security在方法上使用注解实现权限控制,使用自定义UserDetailService,从MySQL中加载用户信息.使用Security自带的MD5加密,对用户密码进行加密.页面模板采用thymeleaf引擎. 源码地址:https://github.com/li5454yong/springboot-security.git 1.引入pom依赖 <parent> <groupId>org.springframework.boot

  • SpringBoot与spring security的结合的示例

    权限控制,也是我们再日常开发中经常遇到的场景,需要根据用户的角色决定是否可以看到某个资源.目前,市面上此类框架主要有shiro与我们今天要讲到的spring security.关于权限的控制有复杂的控制,例如几乎每个公司都有单点登录系统,根据用户名来到数据库中拿到对应的权限,在展示该权限下能看到的资源.还有一种就是简单的控制,也就是我们今天所要提到的.将账号,密码,角色配置到代码中,也可以进行简单的控制,缺点不言而喻,扩展性不好,只有固定的账号,但是作为演示还是够用的. 好了废话不多说,上pom

  • Spring Boot中整合Spring Security并自定义验证代码实例

    最终效果 1.实现页面访问权限限制 2.用户角色区分,并按照角色区分页面权限 3.实现在数据库中存储用户信息以及角色信息 4.自定义验证代码 效果如下: 1.免验证页面 2.登陆页面 在用户未登录时,访问任意有权限要求的页面都会自动跳转到登陆页面. 3.需登陆才能查看的页面 用户登陆后,可以正常访问页面资源,同时可以正确显示用户登录名: 4.用户有角色区分,可以指定部分页面只允许有相应用户角色的人使用 4.1.只有ADMIN觉得用户才能查看的页面(权限不足) 4.2.只有ADMIN觉得用户才能查

  • SpringBoot+Security 发送短信验证码的实现

    在core模块下properties包中创建SmsCodeProperties 在ValidateCodeProperties中new一个SmsCodeProperties对象,并实现getter.setter方法 在core模块下validate包中创建SmsCodeGenerator实现ValidateCodeGenerator接口 创建SmsCodeSender接口,定义发送短信的抽象方法 实现SmsCodeSender接口 在ValidateCodeBeanConfig中把SmsCode

  • SpringBoot + SpringSecurity 环境搭建的步骤

    一.使用SpringBoot+Maven搭建一个多模块项目(可以参考这篇文章 --> 这里) 二.删除父工程的src文件,删除app.browser.core下的.java文件 依赖关系: demo 依赖 browser browser.app依赖core 三.父工程pom.xml文件 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache

  • SpringBoot结合SpringSecurity实现图形验证码功能

    本文介绍了SpringBoot结合SpringSecurity实现图形验证码功能,分享给大家,具体如下: 生成图形验证码 根据随机数生成图片 将随机数存到Session中 将生成的图片写到接口的响应中 生成图形验证码的过程比较简单,和SpringSecurity也没有什么关系.所以就直接贴出代码了 根据随机数生成图片 /** * 生成图形验证码 * @param request * @return */ private ImageCode generate(ServletWebRequest r

  • SpringBoot + Spring Security 基本使用及个性化登录配置详解

    Spring Security 基本介绍 这里就不对Spring Security进行过多的介绍了,具体的可以参考官方文档 我就只说下SpringSecurity核心功能: 认证(你是谁) 授权(你能干什么) 攻击防护(防止伪造身份) 基本环境搭建 这里我们以SpringBoot作为项目的基本框架,我这里使用的是maven的方式来进行的包管理,所以这里先给出集成Spring Security的方式 <dependencies> ... <dependency> <groupI

  • Spring Boot如何使用Spring Security进行安全控制

    我们在编写Web应用时,经常需要对页面做一些安全控制,比如:对于没有访问权限的用户需要转到登录表单页面.要实现访问控制的方法多种多样,可以通过Aop.拦截器实现,也可以通过框架实现(如:Apache Shiro.spring Security). 本文将具体介绍在Spring Boot中如何使用Spring Security进行安全控制. 准备工作 首先,构建一个简单的Web工程,以用于后续添加安全控制,也可以用之前Chapter3-1-2做为基础工程.若对如何使用Spring Boot构建We

  • SpringBoot + SpringSecurity 短信验证码登录功能实现

    实现原理 在之前的文章中,我们介绍了普通的帐号密码登录的方式: SpringBoot + Spring Security 基本使用及个性化登录配置. 但是现在还有一种常见的方式,就是直接通过手机短信验证码登录,这里就需要自己来做一些额外的工作了. 对SpringSecurity认证流程详解有一定了解的都知道,在帐号密码认证的过程中,涉及到了以下几个类:UsernamePasswordAuthenticationFilter(用于请求参数获取),UsernamePasswordAuthentica

  • SpringBoot安全认证Security的实现方法

    一.基本环境搭建 父pom依赖 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.3.RELEASE</version> </parent> 1. 添加pom依赖: <dependency> <groupId&

  • SpringBoot security安全认证登录的实现方法

    目录 前言 一.登录时序图 二.配置与代码 1.引入库 2.代码文件 参考文档 前言 本文章主要从spring security安全认证登录内部调用流程来流程分析登录过程. 一.登录时序图 时序原图 二.配置与代码 1.引入库 pom.xml: <!-- Spring框架基本的核心工具 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-contex

  • SpringBoot集成Spring Security用JWT令牌实现登录和鉴权的方法

    最近在做项目的过程中 需要用JWT做登录和鉴权 查了很多资料 都不甚详细 有的是需要在application.yml里进行jwt的配置 但我在导包后并没有相应的配置项 因而并不适用 在踩过很多坑之后 稍微整理了一下 做个笔记 一.概念 1.什么是JWT Json Web Token (JWT)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519) 该token被设计为紧凑且安全的 特别适用于分布式站点的单点登录(SSO)场景 随着JWT的出现 使得校验方式更加简单便

  • SpringBoot集成Spring Security的方法

    至今Java能够如此的火爆Spring做出了很大的贡献,它的出现让Java程序的编写更为简单灵活,而Spring如今也形成了自己的生态圈,今天咱们探讨的是Spring旗下的一个款认证工具:SpringSecurity,如今认证框架主流"shiro"和"SpringSecurity",由于和Spring的无缝衔接,使用SpringSecurity的企业也越来越多. 1.Spring Security介绍 Spring security,是一个强大的和高度可定制的身份验

  • Springboot集成Spring Security实现JWT认证的步骤详解

    1 简介 Spring Security作为成熟且强大的安全框架,得到许多大厂的青睐.而作为前后端分离的SSO方案,JWT也在许多项目中应用.本文将介绍如何通过Spring Security实现JWT认证. 用户与服务器交互大概如下: 客户端获取JWT,一般通过POST方法把用户名/密码传给server: 服务端接收到客户端的请求后,会检验用户名/密码是否正确,如果正确则生成JWT并返回:不正确则返回错误: 客户端拿到JWT后,在有效期内都可以通过JWT来访问资源了,一般把JWT放在请求头:一次

  • SpringBoot集成Spring security JWT实现接口权限认证

    1.添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjw

  • SpringBoot整合Spring Security的详细教程

    好好学习,天天向上 本文已收录至我的Github仓库DayDayUP:github.com/RobodLee/DayDayUP,欢迎Star,更多文章请前往:目录导航 前言 Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架.提供了完善的认证机制和方法级的授权功能.是一款非常优秀的权限管理框架.它的核心是一组过滤器链,不同的功能经由不同的过滤器.这篇文章就是想通过一个小案例将Spring Security整合到SpringBoot中去.要实现的功能就是在认证服务器上

  • SpringBoot Admin的简单使用的方法步骤

    目录 一.快速入门 1.1 SpringBoot Admin服务端的搭建 1.2 SpringBootAdmin client端搭建 1.3 效果展示 二.安全性 2.1 admin-server端安全加固 2.2 admin-client端的安全 三.小结 公司有个SpringBoot项目需要加个监控,网上找了下发现大家都在推荐SpringBootAdmin.SpringBoot Admin是开源社区孵化的项目,用于对SpringBoot应用的管理和监控.SpringBoot Admin 分为

  • SpringBoot整合Spring Security过滤器链加载执行流程源码分析(最新推荐)

    目录 1.引言 2.Spring Security过滤器链加载 2.1.注册名为 springSecurityFilterChain的过滤器 3.查看 DelegatingFilterProxy类 4.查看 FilterChainProxy类 4.1 查看 doFilterInternal方法 4.2 查看 getFilters方法 5 查看 SecurityFilterChain接口 6. 查看 SpringBootWebSecurityConfiguration类 总结: 1.引言 在 Sp

  • 使用spring-boot-admin对spring-boot服务进行监控的实现方法

    spring-boot-admin,简称SBA,是一个针对spring-boot的actuator接口进行UI美化封装的监控工具.他可以:在列表中浏览所有被监控spring-boot项目的基本信息,详细的Health信息.内存信息.JVM信息.垃圾回收信息.各种配置信息(比如数据源.缓存列表和命中率)等,还可以直接修改logger的level. 官网:https://github.com/codecentric/spring-boot-admin 使用指南:http://codecentric.

随机推荐