《spring boot +spring security安全框架》
1.简单篇:
|-导入pom 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
|-配置config
*配置类添加@EnableWebSecurity 注解
*继承 WebSecurityConfigurerAdapter
@EnableWebSecurity //注解开启Spring Security的功能
public class SecurityConfig extends WebSecurityConfigurerAdapter {}
*设置用户,基于内存
//用户验证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
//基于内存来存储用户信息
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("user").password(new BCryptPasswordEncoder().encode("pwd")).roles("USER").and() //设置
.withUser("admin").password(new BCryptPasswordEncoder().encode("pwd")).roles("USER","ADMIN");
}
*设置http请求权限认证
//用户权限认证
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//这是认证的请求
.authorizeRequests()
.antMatchers("/","/home","/login","/index","/error").permitAll() //这些请求 不需要认证
.antMatchers("/user/**").hasRole("USER") //user及以下路径,需要USER权限
.antMatchers("/admin/**").hasRole("ADMIN")
.and()
.formLogin().loginPage("/login").defaultSuccessUrl("/") //登录页面为login,登录成功跳转 "/"
.and()
.logout().logoutUrl("/logout").logoutSuccessUrl("/"); //等出路径为logout,登出成功跳转 "/"
}
*忽略 静态资源
/**
* 核心过滤器配置,更多使用ignoring()用来忽略对静态资源的控制
*/
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/image/**");
}
2.添加后门backdoor,例如输入:alex/任意密码就可以通过验证
|- 新建类SecurityProvider类实现AuthenticationProvider接口
|- 实现authenticate()和supports()方法
/**
* 自定义验证类,可以用作backdoor,例如输入:alex/任意密码就可以通过验证
*/
@Component
public class SecurityProvide implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
//利用alex用户名登录,不管密码是什么都可以,伪装成admin用户
if (name.equals("alex")) {
Collection<GrantedAuthority> authorityCollection = new ArrayList<>();
authorityCollection.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
authorityCollection.add(new SimpleGrantedAuthority("ROLE_USER"));
return new UsernamePasswordAuthenticationToken(
"admin", password, authorityCollection);
} else {
return null;
}
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(
UsernamePasswordAuthenticationToken.class);
}
}
|-添加自定义的认证/验证方式
//用户验证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
//基于内存来存储用户信息
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("user").password(new BCryptPasswordEncoder().encode("pwd")).roles("USER").and()
.withUser("admin").password(new BCryptPasswordEncoder().encode("pwd")).roles("USER","ADMIN");
auth.authenticationProvider(securityProvide);
}
|-添加自定义参数名myusername和mypassword
//用户权限认证
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//这是认证的请求
.authorizeRequests()
//这是不需要认证的请求
.antMatchers("/","/home","/login","/index","/error").permitAll()
.antMatchers("/user/**").hasRole("USER")
.antMatchers("/admin/**").hasRole("ADMIN")
.and()
.formLogin().loginPage("/login").defaultSuccessUrl("/")
.usernameParameter("myusername").passwordParameter("mypassword")
.and()
.logout().logoutUrl("/logout").logoutSuccessUrl("/");
}
3.连接数据库user信息
|- entity中user对象实现 UserDetails 接口,并实现其下 7 个方法
*getAuthorities()
/**
* 从数据库中取出roles字符串后,进行分解,构成一个GrantedAuthority权限集合的List返回
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities =new ArrayList<>();
String[] split = roles.split(",");
for (String s: split) {
authorities.add(new SimpleGrantedAuthority(s)); //添加权限
}
return authorities;
}
*获得 username 与 password 用户信息
@Override
public String getPassword() {return password;}
@Override
public String getUsername() {return username;}
*其余四个方法 都是以 true 返回
|-service层实现类实现 UserDetailsService 接口,并实现 loadUserByUsername 方法
//查询数据库 用户信息 进行验证
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User s1 =userMapper.findByUsername(username);
if (s1==null) throw new UsernameNotFoundException("数据库中无此用户!");
return s1;
}
|- SecurityConfig 中添加 service 验证方式
@Autowired
UserServiceImpl userService //必须注入 实现UserDetailsService接口的实现层
...
auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
...
4.添加自定义鉴权管理器+异常处理类+权限元数据获取
|-自定义鉴权管理器MyAccessDecisionManager 实现AccessDecisionManager+@Component
package com.deceen.config;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Iterator;
/**
* 鉴权控制器
*/
@Component
public class MyAccessDecisionManager implements AccessDecisionManager {
//进行权限 验证
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
//从configAttributes中获取访问资源所需要的角色,它来自MySecurityMetadataSource的getAttributes
Iterator<ConfigAttribute> iterator = configAttributes.iterator(); //这是来自resource中
while (iterator.hasNext()){
ConfigAttribute authority = iterator.next();
String resourceRole = authority.getAttribute();
if (resourceRole.equals("ROLE_NONE")){
if(authentication instanceof AnonymousAuthenticationToken){
throw new BadCredentialsException("用户未登录");
}
return;
}
//用户携带的 权限 -----> Authentication authentication,authentication.getAuthorities().getAuthority();
//资源中自定义的 权限 -----> Collection<ConfigAttribute> configAttributes.getAttribute()
Iterator<? extends GrantedAuthority> iterator1 = authentication.getAuthorities().iterator(); //这是用户身上携带的角色
while (iterator1.hasNext()){
String userRole = iterator1.next().getAuthority();
if (userRole.equals("ROLE_ADMIN")){
return;
}else if(userRole.equals(resourceRole)){
return;
}
}
}
throw new AccessDeniedException("你没有访问:"+object+"权限");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
|-权限元数据获取 实现 FilterInvocationSecurityMetadataSource+@Component
package com.deceen.config;
import com.deceen.entity.Resource;
import com.deceen.service.ResourceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import java.util.Collection;
import java.util.List;
/**
* 用于鉴权中,获取的元数据,resource中对应的 访问路径,权限
*/
@Component
public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
@Autowired
private ResourceService resourceService;
//本方法返回访问资源所需的角色集合
AntPathMatcher antPathMatcher = new AntPathMatcher();
//获取访问路径所对应的权限
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
//从object中得到需要访问的资源,即网址
String url = ((FilterInvocation) object).getRequestUrl();
List<Resource> all = resourceService.findAll();
for (Resource resource:all) {
if (antPathMatcher.match(resource.getUrl(),url)&& resource.getArrayRoles().length>0){
return SecurityConfig.createList(resource.getArrayRoles());
}
}
return SecurityConfig.createList("ROLE_NONE");
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
|-权限异常处理类 实现AccessDeniedHandler+@Component
package com.deceen.config;
import com.deceen.entity.vo.JsonResult;
import com.deceen.utils.ObjectMapperUtil;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 验证异常 处理类
*/
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
httpServletResponse.setContentType("application/json,charset=UTF-8");
PrintWriter writer = httpServletResponse.getWriter();
JsonResult info = new JsonResult(500,e.getMessage(),null);
writer.write(ObjectMapperUtil.toJson(info));
writer.flush();
writer.close();
}
}
|-修改配置类里的 用户权限认证方法
//用户权限认证
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//这是认证的请求
.authorizeRequests()
// //这是不需要认证的请求
// .antMatchers("/","/home","/login","/index","/error").permitAll()
// .antMatchers("/user/**").hasRole("USER")
// .antMatchers("/admin/**").hasRole("ADMIN")
//添加自定义 权限拦截器 FilterSecurityInterceptor 识别
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setSecurityMetadataSource(mySecurityMetadataSource);
object.setAccessDecisionManager(myAccessDecisionManager);
return object;
}
})
.and()
.formLogin().loginPage("/login_p").loginProcessingUrl("/login").permitAll()
//1.自定义参数名称,与login.html中的参数对应
.usernameParameter("myusername").passwordParameter("mypassword")
.and()
.logout().logoutUrl("/logout").logoutSuccessUrl("/login").permitAll()
.and()
.csrf().disable()
.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);
}
5.几种获得当前请求中用户的数据
*在Controller层,通过@AuthenticationPrincipal Principal principal(注解可省略)
@GetMapping("/user")
public String user(@AuthenticationPrincipal Principal principal, Model model) {
model.addAttribute("username",principal.getName());
return "user/user";
}
*在Controller层,通过 HttpServletRequest
@RequestMapping(value = "/username", method = RequestMethod.GET)
@ResponseBody
public String name(HttpServletRequest request) {
Principal principal = request.getUserPrincipal();
return principal.getName();
}
*在任何地方,通过注入 IAuthenticationFacade
@Autowired
private IAuthenticationFacade authenticationFacade;
@RequestMapping(value = "/username", method = RequestMethod.GET)
@ResponseBody
public String nameSimpe() {
Authentication authentication = authenticationFacade.getAuthentication();
return authentication.getName();
}
*在任何地方,通过SecurityContextHolder中的.getContext().getAuthentication()
@GetMapping("/admin")
public String admin( Model model) {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String username=null;
if (principal instanceof UserDetails) {
username= ((UserDetails) principal).getUsername();
}else if (principal instanceof Principal){
username= ((Principal) principal).getName();
}
model.addAttribute("username",username);
return "admin/admin";
}
转载:https://zhuanlan.zhihu.com/p/47224331