security会对服务端的接口添加权限过滤,不具备权限的请求将被拒绝。
要引入security,只需添加依赖即可:
在pom.xml中引入security:
<dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-securityartifactId>
dependency>
运行工程&#xff0c;控制台会生成一个随机的密码&#xff1a;
当用浏览器访问服务端接口时&#xff0c;会提示输入用户名和密码。用户名为user&#xff0c;密码即为生成的随机密码。该验证方式为httpSecurity basic
验证。
若要直接调用接口访问&#xff0c;则需要在请求头的Authorization
中附加用户名/密码。
使用postman时&#xff0c;需要将请求的Authorization
属性的TYPE设置为Basic Auth。
若要关闭该验证功能&#xff0c;则为Application启动类的&#64;SpringBootApplication
注解添加排除类SecurityAutoConfiguration.class
&#xff1a;
&#64;SpringBootApplication(exclude&#61;SecurityAutoConfiguration.class)
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}
}
但该方式的优先级低于配置类。若配置类设置了开启验证&#xff0c;则依然还是需要进行验证。
在application.properties中添加属性&#xff1a;
spring.security.user.name&#61;user
spring.security.user.password&#61;123
这样&#xff0c;就将security验证的用户名和密码修改为user
和123
。但这样意味着登录系统的用户名和密码是固定的&#xff0c;不推荐。
若使用配置类&#xff0c;则可以不用在application.properties中添加属性&#xff0c;而是直接在配置类中设置。
配置类优先级更高。若配置类中没有设置用户名和密码&#xff0c;则使用application.properties中的设置。
添加一个配置类&#xff1a;
&#64;EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {// 设置密码验证方式&#64;Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}// 设置对各个接口的访问过滤&#64;Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception {httpSecurity.authorizeRequests() // 对请求进行授权.antMatchers("/login","/version").permitAll() // 设置不需要认证的请求.antMatchers("/test").hasAuthority("p1") // 设置/test接口需要p1权限.anyRequest().authenticated() // 设置所有请求都需要认证.and() // 上一项配置已经结束&#xff0c;下一项配置将开始.httpBasic(); // 开启httpBasic登录httpSecurity.csrf().disable(); // 关闭默认csrf认证}// 设置对静态资源的访问过滤&#64;Overridepublic void configure(WebSecurity web) throws Exception {// 设置不拦截的静态资源web.ignoring().antMatchers("/file/**","/images/**");}
}
其中:
&#64;EnableWebSecurity
:开启Security功能&#xff0c;从而支持Security的注解。passwordEncoder()
的返回可设置为NoOpPasswordEncoder.getInstance()
。antMatchers()
:可传入多个String
参数&#xff0c;用于匹配多个请求API。结合permitAll()
&#xff0c;可同时对多个请求设置忽略认证。and()
:用于表示上一项配置已经结束&#xff0c;下一项配置将开始。然后创建一个Service文件用于Security查询用户信息&#xff1a;
&#64;Service
public class SpringDataUserDetailsService implements UserDetailsService {&#64;AutowiredUserDao userDao;// 根据账号查询用户信息&#64;Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {UserDto userDto &#61; userDao.getUserByUsername(username);if(userDto &#61;&#61; null){// 如果用户查不到&#xff0c;返回null&#xff0c;由provider来抛出异常return null;}// 根据用户的id查询用户的权限List<String> permissions &#61; userDao.findPermissionsByUserId(userDto.getId());//将permissions转成数组String[] permissionArray &#61; new String[permissions.size()];permissions.toArray(permissionArray);UserDetails userDetails &#61; User.withUsername(userDto.getUsername()).password(userDto.getPassword()).authorities(permissionArray).build();return userDetails;}
}
当用户请求时&#xff0c;Security便会拦截请求&#xff0c;取出其中的username字段&#xff0c;从Service中查询该账户&#xff0c;并将信息填充到一个userDetails
对象中返回。这样Security便拿到了填充后的userDetails
对象&#xff0c;也就是获取到了包括权限在内的用户信息。
Security默认开启session。
配置好之后&#xff0c;当用户请求时&#xff0c;会要求先进行登录。若登陆成功&#xff0c;就会返回给用户一个session。
之后当用户访问接口时&#xff0c;每次都会对请求进行拦截过滤&#xff0c;取出session进行查询判断&#xff0c;验证是否具有接口的访问权限。
若有权限&#xff0c;则返回正常结果&#xff1b;否则返回403错误。
由于使用session&#xff0c;因此退出登录需要将请求中的session清除&#xff1a;
&#64;RequestMapping("/logout")
public String logout(HttpSession session){session.invalidate();return "退出成功";
}
然后设置httpSecurity.logout().logoutUrl("/logout")
&#xff0c;注意logoutUrl需要设置为自定义的接口。
WebSecurityConfigurerAdapter.configure(HttpSecurity httpSecurity)
相关配置文件中&#xff0c;configure(HttpSecurity httpSecurity)
用于定义一系列的过滤规则。
整个http.xxx的校验是按照配置的顺序从上往下进行。若某个条件已匹配过&#xff0c;则后续同条件的匹配将不再被执行。也就是说&#xff0c;若多条规则是相互矛盾的&#xff0c;则只有第一条规则生效。典型地&#xff1a;
// 只有具有权限a的用户才能访问/get/resource
httpSecurity.authorizeRequests().antMatchers("/get/resource").hasAuthority("a").anyRequest().permitAll();// 所有用户都能访问/get/resource
httpSecurity.authorizeRequests().anyRequest().permitAll().antMatchers("/get/resource").hasAuthority("a");
但若是包含关系&#xff0c;则需要将细粒度的规则放在前面&#xff1a;
// /get/resource需要权限a&#xff0c;除此之外所有的/get/**都可以随意访问
httpSecurity.authorizeRequests().antMatchers("/get/resource").hasAuthority("a").antMatchers("/get/**").permitAll();
授权有两种方式&#xff1a;
configure(HttpSecurity httpSecurity)
中设置。&#64;Override
httpSecurity.authorizeRequests() // 对请求进行授权.antMatchers("/test1").hasAuthority("p1") // 设置/test1接口需要p1权限.antMatchers("/test2").hasAnyRole("admin", "manager") // 设置/test2接口需要admin或manager角色.and() // 上一项配置已经结束&#xff0c;下一项配置将开始.httpBasic(); // 开启httpBasic登录
其中&#xff1a;
hasAuthority("p1")
表示需要p1权限hasAnyAuthority("p1", "p2")
表示p1或p2权限皆可同理&#xff1a;
hasRole("p1")
表示需要p1权限hasAnyRole("p1", "p2")
表示p1或p2权限皆可&#64;PreAuthorize("hasAuthority(&#39;p&#39;)")// 拥有p权限才可以访问
public String getResource(){return "访问资源";
}
在其中可以使用表达式&#xff1a;
&#64;PreAuthorize("#user.name.equals(&#39;admin&#39;)")
public void getResource(User user) {System.out.println("只有管理员才能获取到的数据");
}
也可以基于角色&#xff1a;
&#64;PreAuthorize("hasAnyRole(&#39;admin&#39;, &#39;manager&#39;)")
public void getResource(User user) {System.out.println("只有管理者才能获取到的数据");
}
访问接口时&#xff0c;默认会打开一个登录页面&#xff0c;这就是表单登录。
httpSecurity.authorizeRequests().and().formLogin()//允许表单登录.successForwardUrl("/login-success");//自定义登录成功的页面地址
其中formLogin()
表示允许表单登录。当调用了该方法&#xff0c;即会显示登录页面。
表单登录默认开启。若重载configure(HttpSecurity httpSecurity)
且在其中没有调用formLogin()
&#xff0c;则表单登录会关闭。此时可以启用httpBasic
:
&#64;Override
httpSecurity.httpBasic(); // 开启httpBasic登录
这样浏览器访问时会弹窗提示输入用户名和密码。
若不需要表单登录&#xff0c;那么不附加formLogin()
和httpBasic()
。
httpSecurity.logoutUrl()
和httpSecurity.addLogoutHandler()
是冲突的&#xff0c;通常只用其中之一。
对于使用token且服务端不进行存储的情况&#xff0c;不需要请求服务端进行登出&#xff0c;直接由前端将token丢弃即可。
要解决跨域问题&#xff0c;通常需要&#xff1a;
httpSecurity.cors().and().csrf().disable();
其中&#xff1a;
cors()
允许跨域资源访问。csrf().disable()
禁用跨域安全验证。httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
&#xff1a;指的是设置session的创建策略。包含4个值&#xff1a;
Security默认使用的的策略为IF_REQUIRED
。若使用token的方式&#xff0c;意味着不需要session&#xff0c;那么就需要设为STATELESS
。
可基于Security提供的抽象类Filter创建自定义Filter&#xff0c;然后调用HttpSecurity
的成员方法来添加到filterChain中&#xff0c;例如&#xff1a;
定义Filter&#xff1a;
public class MyFilter extends OncePerRequestFilter {&#64;Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws IOException, ServletException {filterChain.doFilter(httpServletRequest, httpServletResponse);}
}
然后添加到filterChain&#xff1a;
&#64;Override
protected void configure(HttpSecurity httpSecurity) throws Exception {// 自定义token过滤器httpSecurity.addFilterBefore(new MyFilter(), UsernamePasswordAuthenticationFilter.class)
}
添加的方法除了addFilterBefore()
&#xff0c;还有addFilter()
、addFilterAfter()
、addFilterAt()
等。