SpringSecurity=Authentication + Authorization
先上图
流程简介说明
别人的
相关的类图
画画花了好大一会功夫,以上纯属个人理解
介绍一下配置
用常用需求为例
1、特简单的web项目,几个Controller,2,3个角色,或者临时项目
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password("123").roles("ADMIN")
.and()
.withUser("user")
.password("123").roles("USER");
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/js/**", "/css/**","/images/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/user/**").hasRole("USER")
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll()
.and()
.csrf().disable();
}
}
说明:登录地址与操作都是login
2、前后端不分离,身份认证从库中对比
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 修改相应的密码比对方式
* @return
*/
@Bean
PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Autowired
UserService userService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/js/**", "/css/**","/images/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/user/**").hasRole("USER")
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll()
.and()
.csrf().disable();
}
}
说明:重点就在UserService类,实现
security.core.userdetails.UserDetailsService中的loadUserByUsername方法,根据用户名查询用户的信息及权限返回UserDetails
用户类必须继承security.core.userdetails.UserDetails
3、前后端分离,身份认证从库中对比,登录从表单改为Json,有验证码或者通过手机验证码动态登录
登录改成Json
自定义Filter继承自UsernamePasswordAuthenticationFilter
其实只要修改这个地方即可。
验证码或者通过手机验证码动态登录
方法一、写在前面的登录转json的Filter中,从request中取得数据,对比,不对直接抛出异常
方法二、自定义类继承AbstractUserDetailsAuthenticationProvider or DaoAuthenticationProvider,重写认证和密码比对方法
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 修改相应的密码比对方式
* @return
*/
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
UserService userService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/js/**", "/css/**","/images/**");
}
@Bean
MyLoginFilter loginFilter() throws Exception {
MyLoginFilter loginFilter = new MyLoginFilter();
loginFilter.setAuthenticationSuccessHandler((req, resp, authentication) -> {
Map<String,Object> map = new HashMap<>();
map.put("user",authentication.getPrincipal());
ResultUtil.responseJson(resp,ResultUtil.resultSuccess(map));
});
loginFilter.setAuthenticationFailureHandler((req, resp, failed) -> {
String msg;
if(failed instanceof LockedException){
msg = "账户被锁定,请联系管理员!";
}else if (failed instanceof BadCredentialsException){
msg = "用户名或者密码输入错误,请重新输入!";
}else if(failed instanceof DisabledException){
msg = "账户被禁用,请联系管理员!";
}else if (failed instanceof AccountExpiredException){
msg = "账户过期,请联系管理员!";
}else if (failed instanceof CredentialsExpiredException){
msg = "密码过期,请联系管理员!";
}else{
msg = "other";
}
resp.setStatus(400);
ResultUtil.responseJson(resp,ResultUtil.resultCode(400,msg));
});
loginFilter.setAuthenticationManager(authenticationManagerBean());
return loginFilter;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/test/user").hasRole("USER")
.antMatchers("/test/admin").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.logout()
.logoutSuccessHandler((req,resp,authentication) -> {
ResultUtil.responseJson(resp,ResultUtil.resultCode(200,"注销登录成功"));
})
.and().csrf().disable()
.exceptionHandling().authenticationEntryPoint((req,resp,exception) -> {
resp.setStatus(401);
ResultUtil.responseJson(resp,ResultUtil.resultCode(401,"尚未登录,请登录"));
});
http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
授权
方式一
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasAnyRole("ADMIN", "USER")
.anyRequest().authenticated()
.and()
...
}
方式二
启动注解
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
@PreAuthorize("hasRole('USER')")
@GetMapping(value="/delete")
public HttpResult delete() {
return HttpResult.ok("the delete service is called success.");
}
@PreAuthorize("#age>98")
@GetMapping(value="/getAge")
public HttpResult getAge(Integer age){
return HttpResult.ok("the getAge service is called success.");
}
@PreAuthorize("hasRole('USER') and hasRole('ADMIN')")
@PreAuthorize("principal.username.equals('admin')")
@PreAuthorize("hasAuthority('sys:notesDevice:selectAll')")
其实就是表达式
用hasAuthority或者用hasRole,取决于UserDetails的
private Collection<? extends GrantedAuthority> authorities;
如果authorities,中的存的是角色,必须是ROLE_开头,可以使用hasRole,如果是具体的权限串用hasAuthority,其实二个内部实现逻辑一样
方式三
过滤注解,个人理解,PreAuthorize对这一个方法而言,要不拒绝要不通过,而PreFilter则是方法的部分数据通过或者拒绝,PreFilter在方法前,PostFilter在方法后
@PostFilter("filterObject.lastIndexOf('H')!=-1")
public List<String> testList() {
//.....
return list;
}
@PreFilter(filterTarget = "ids",value = "filterObject % 2 == 0")
@GetMapping("/filter")
public String filter(@RequestParam("ids") List<Integer> ids,@RequestParam("name") String name){
return name+"_filter"+ids;
}
方法四
启用hasPermission,灵活设置
PermissionEvaluator 是为表达式 hasPermission 提供支持的。
config中添加
@Bean
public DefaultWebSecurityExpressionHandler userSecurityExpressionHandler(){
DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
handler.setPermissionEvaluator(new MyPermissionEvaluator());
return handler;
}
public class MyPermissionEvaluator implements PermissionEvaluator {
/**
* 注入Service
*/
@Autowired
private UserService userService;
/**
* 灵活设置
*
* @param authentication
* @param targetDomainObject hasPermission第一个参数
* @param permission 第二个参数
* @return true允许、false拒绝
*/
@Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
return false;
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
return false;
}
}
@PreAuthorize("hasRole('ADMIN') and hasPermission('/user/info','sys:user:info')")
方法五
动态权限,下回分解