SpringSecurity的学习成本比较高,API比较难懂,这里记录一下用SpringSecurity实现项目的登录各种需求
首先实现自定义用户登录与自定义权限的session模式,session其实就是存储在服务器上的用户信息,用户登录后会返回一个sessionID存储到用户浏览器的cookie里,用户下次访问携带sessionID,服务器就能知道用户是否已经登录过。
那么SpringSecurity是如何实现用户登录和权限校验的呢?下面贴出主要代码:
先贴pom依赖
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-security
org.springframework.boot
spring-boot-starter-jdbc
com.baomidou
mybatisplus-spring-boot-starter
1.0.5
com.baomidou
mybatis-plus
2.1.8
mysql
mysql-connector-java
5.1.39
com.alibaba
druid
1.0.26
这里使用的SpringBoot版本是2.1.0.RELEASE,引入了SpringSecurity和mybatis,web,等
项目目录
不要在意项目名称,这个本来是oauth2.0的项目,为了方便在这个项目上改的,所以才会起名叫oauth
看目录结构,目录比较多,但是和security相关的目录只有2个,config和security
config目录里只有一个文件,SecurityConfig,处理逻辑都是写到这里的,贴代码:
/**
* @program: springcloud
* @description: SpringSecurity的配置文件
* @author: paddy
* @create: 2019-04-15 12:47
**/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 启用方法级别的权限认证
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailsService myUserDetailsService;
@Autowired
private MyInvocationSecurityMetadataSourceService myInvocationSecurityMetadataSourceService;
@Autowired
private MyAccessDecisionManager myAccessDecisionManager;
//过滤器的配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor() {
@Override
public O postProcess(O o) {
o.setSecurityMetadataSource(myInvocationSecurityMetadataSourceService);
o.setAccessDecisionManager(myAccessDecisionManager);
return o;
}
})
.antMatchers("/","/login","/index")// 允许直接访问的地址
.permitAll()
.anyRequest().authenticated() // 其他地址的访问均需验证权限
.and()
.formLogin()
.loginPage("/login") // 登录页
.successForwardUrl("/index") // 成功后跳转页面
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/index");
}
//AuthenticatManager的配置
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new PasswordEncoderImpl();
}
}
这个文件注入了4个自定义的实现类:
MyUserDetailsService -- 登录认证类
MyInvocationSecurityMetadataSourceService -- 提供访问uri所需要的权限
MyAccessDecisionManager -- 权限校验逻辑
PasswordEncoderImpl -- 加密规则,SpringSecurity默认不支持md5加密,这里自定义一个md5加密
security文件夹下就是这些自定义的类,分别贴出代码
MyUserDetailsService -- SpringSecurity的登录认证使用UserDetailsService,这个类就实现了UserDetailsService接口,重写逻辑
,实现代码如下
/**
* @program: springcloud
* @description: 自定义的UserDetailsService
* @author: paddy
* @create: 2019-04-15 12:47
**/
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private ISysUserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserVo userVo = userService.selectVoByLoginName(username); // 查询用户
if (userVo == null ) { // 如果没查询到返回错误
throw new UsernameNotFoundException("用户名称错误!");
}
List authorities = new ArrayList<>();
Iterator iRole = userVo.getRolesList().iterator();
while (iRole.hasNext()){ // 添加查询到的角色信息
authorities.add(new SimpleGrantedAuthority(iRole.next().getName()));
}
return new User(userVo.getLoginName(), userVo.getPassword(),authorities);
}
}
这里查询出用户信息,把查询出的UserVo存到SpringSecurity提供的User类里,User的参数分别为用户名,密码,角色信息。然后SpringSecurity会帮我们用这个User里的用户名和密码和前台传过来的用户名密码做比对。
PasswordEncoderImpl 这个没什么好说的,就是自己实现md5加密,贴代码:
/**
* @program: springcloud
* @description: 自定义的密码处理逻辑,采用md5加密
* @author: paddy
* @create: 2019-04-15 12:46
**/
public class PasswordEncoderImpl implements PasswordEncoder {
@Override
public String encode(CharSequence charSequence) {
try {
// MD5加密密码
return DigestUtils.md5Hex(charSequence.toString().getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return encode(charSequence).equals(s);
}
}
MyInvocationSecurityMetadataSourceService -- SpringSecurity的权限数据使用FilterInvocationSecurityMetadataSource,这个类实现了FilterInvocationSecurityMetadataSource接口,在这里重写逻辑
/**
* @program: springcloud
* @description: 自定义的Metadata,通过当前请求地址,获取该地址所需要的角色
* @author: paddy
* @create: 2019-04-15 12:56
**/
@Service
public class MyInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource {
@Autowired
ISysResourceService resourceService;
@Override
public Collection getAttributes(Object o) throws IllegalArgumentException {
//object 中包含用户请求的request 信息
HttpServletRequest request = ((FilterInvocation) o).getHttpRequest();
String uri = request.getRequestURI();
List roleNameList = resourceService.selectRoleNamesByURI(uri); // 查出资源所需要的角色
if(roleNameList!=null){
List attributes = new ArrayList();
for(String roleName:roleNameList){
attributes.add(new SecurityConfig(roleName));
}
return attributes;
}
return null;
}
@Override
public Collection getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class> aClass) {
return true;
}
}
逻辑很简单,通过访问的uri查询数据库,得到访问某个uri所需要的权限信息
MyAccessDecisionManager -- SpringSecurity的权限数据使用AccessDecisionManager,这个类实现了AccessDecisionManager接口,在这里重写逻辑
/**
* @program: springcloud
* @description: 自定义的AccessDecisionManager,用来判断权限
* @author: paddy
* @create: 2019-04-15 14:39
**/
@Service
public class MyAccessDecisionManager implements AccessDecisionManager {
/**
*
* @param authentication 当前用户所具有的权限
* @param o
* @param configAttributes 当前访问所需要的权限
* @throws AccessDeniedException
* @throws InsufficientAuthenticationException
*/
@Override
public void decide(Authentication authentication, Object o, Collection configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
Iterator iterator = configAttributes.iterator();
while (iterator.hasNext()) {
ConfigAttribute ca = iterator.next();
//当前请求需要的权限
String needRole = ca.getAttribute();
//当前用户所具有的权限
Collection extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
if (authority.getAuthority().equals(needRole)) {
return;
}
}
}
throw new AccessDeniedException("权限不足!");
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
return true;
}
@Override
public boolean supports(Class> aClass) {
return true;
}
}
这个类的注释写的很清楚,通过上一个自定义接口MyInvocationSecurityMetadataSourceService 获取uri所需要的角色,然后在和已登录的用户信息里存的角色比对。