作者:Zhang
参考资料:《深入理解SpringCloud与微服务构建》
Spring Security是Spring Resource社区的一个安全组件。
Spring Security可以在Controller层,Service层,DAO层等以加注解的方式劳保护应用程序的安全。
Spring Security提供了细粒度的权限控制,可以精细到每一个API接口、每一个业务的方法,或者每一个操作数据库的DAO层的方法。
选择Spring Security的原因有很多,其中一个重要的原因是对环境的无依赖性,低代码耦合性。将工程重新部署到一个新的服务器上,不需要为Spring Security做什么工作。模块与模块间的耦合性低,模块之间可以自由组合来实现特定的需求的安全功能。
安全方面:Spring Security提供了两个主要的领域。一是“认证”,而是“授权”
为什么选择Spring Security作为微服务开发的安全框架呢?
Apache Shiro在企业及项目开发中十分受欢迎,一般使用在单体服务中。
Spring Security已于应用于SpringBoot项目,也易于集成到采用SpringCloud够早的微服务系统中。
在Spring Security框架中,主要包含两个依赖Jar,分别是spring-security-web依赖和spring-security-config依赖,代码如下:
org.springframework.security
spring-security-web
5.0.9.RELEASE
org.springframework.security
spring-security-config
5.0.9.RELEASE
SpringBoot Security则对两个jar包进行了封装。spring-boot-starter-security依赖,代码如下:
org.springframework.boot
spring-boot-starter-security
2.0.4.RELEASE
4.0.0
org.example
SpringBootSecurityDemo
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-web
2.0.4.RELEASE
org.springframework.boot
spring-boot-starter-test
2.0.4.RELEASE
org.springframework.boot
spring-boot-starter-security
2.0.4.RELEASE
/**
* Title: SecurityConfig
* Description:
* SpringBoot Security 配置类
*
*
* @author Zhang
* @date 2020/6/18 0027 17:11
*/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启标签
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception{
// 设置验证信息
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("admin").password(new BCryptPasswordEncoder().encode("1234"))
.roles("USER");
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("zhangsan").password(new BCryptPasswordEncoder().encode("1234"))
.roles("VISITOR");
}
}
/**
* Title: SecurityConfig
* Description:
* SpringBoot Security 配置类
*
*
* @author Zhang
* @date 2020/6/18 0027 17:11
*/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启标签
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() // 配置验证路径信息
/**
* 配置需要验证的路径 可以通过HttpMethod.GET添加条件
* 还有其他如:
* .antMatchers("/sec/common/**").permitAll() 无条件允许访问
* .anyRequest().authenticated() 所有请求都需要进行验证
*/
//.antMatchers("/sec/user/**").authenticated()
.antMatchers("/sec/user/**").hasRole("USER")
.antMatchers("/sec/common/**").permitAll()
.and()
/**
* 登录表单相关:
* .formLogin() //如果参数为空,则采用默认的登录页面
* .formLogin().loginPage("/sec/notLogin") // 自定义登录页面
* .loginProcessingUrl("/login-in") // 自定义登录页面的表单登录提交路径
* .defaultSuccessUrl("/sec/login-success") // 登录成功后跳转
* .failureUrl("/sec/login-error") // 登录失败跳转
*/
.formLogin()
// .formLogin().loginPage("/sec/notLogin") // 登录页面 没有的话就是采用默认的登录页面
.loginProcessingUrl("/login-in") // 登录表单的action
/**
* 登录成功跳转:
* 登录成功,如果是直接从登录页面登录,会跳转到该URL;
* 如果是从其他页面跳转到登录页面,登录后会跳转到原来页面。
* 可设置true来任何时候到跳转 .defaultSuccessUrl("/hello2", true);
*/
.defaultSuccessUrl("/status/login-success") // 登录成功后跳转
.failureUrl("/status/login-error") // 登录失败跳转
.and()
.exceptionHandling().accessDeniedPage("/status/not-login")
.and()
/**
* 设置session策略
*/
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.httpBasic().and().csrf().disable(); //post请求需要csrf验证 disable()表示关闭
}
}
@Controller
@RequestMapping(value = "/sec")
public class MainController {
@RequestMapping("/user/{id}")
public void index(@PathVariable String id){
System.out.println(id);
}
@RequestMapping("/user")
public void test(){
System.out.println(1);
}
@RequestMapping("/login-success")
@ResponseBody
public String loginSuccess(){
System.out.println("登录成功");
return "登录成功";
}
@RequestMapping("/401")
@ResponseBody
public String noAccess(){
System.out.println("无权限");
return "无权限";
}
}
首要要在配置类中开启标签的使用。
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启标签
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
}
再放发前使用:
@PreAuthorize("hasRole('USER')")
@RequestMapping("/user")
public void test(){
System.out.println(1);
}
通过使用JPA的方式创建数据库。
org.springframework.boot
spring-boot-starter-data-jpa
mysql
mysql-connector-java
5.1.47
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3307/crm?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&serverTimezone=GMT%2B8
username: root
password: Shuaizi521.
jpa:
hibernate:
ddl-auto: update
show-sql: true
User.java
@Entity
public class User implements UserDetails, Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column
private String password;
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id",referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))
private List<Role> authorities;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
public String getPassword() {
return password;
}
public String getUsername() {
return username;
}
public boolean isAccountNonExpired() {
return true;
}
public boolean isAccountNonLocked() {
return true;
}
public boolean isCredentialsNonExpired() {
return true;
}
public boolean isEnabled() {
return true;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", authorities=" + authorities +
'}';
}
}
Role.java
@Entity
public class Role implements GrantedAuthority {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Override
public String toString() {
return name;
}
public String getAuthority() {
return name;
}
}
@Repository
public interface UserRepo extends JpaRepository<User, Long> {
/**
* 根据用户名查询用户信息
* @param userName 用户名
* @return
*/
User findByUsername(String userName);
}
编写业务层,这里的业务层实现了UserDetailsService接口,该几口是根绝用户名获取该用户的所有信息,包括用户信息和权限点。
@Service
public class UserService implements UserDetailsService {
@Autowired
UserRepo userRepo;
/**
* 根据用户名获取该用户的所有信息,包括用户信息和权限点。
* @param userName 用户名
* @return
* @throws UsernameNotFoundException
*/
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
return userRepo.findByUsername(userName);
}
}
修改Sercurity配置类,使其数据从数据库中获取。
@EnableWebSecurity
@Configurable
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsService userDetailsService;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception{
// // 设置验证信息
// auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
// .withUser("admin").password(new BCryptPasswordEncoder().encode("1234"))
// .roles("USER");
// auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
// .withUser("zhangsan").password(new BCryptPasswordEncoder().encode("1234"))
// .roles("VISITOR");
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
...
}
启动项目,JPA会连接数据库自动进行建表。