权限-说说SpringSecurity
想想以前为了赶课设的自己,当时用的Shiro,现在用SpringSecurity去探索了一番。以Layui作为前端为例子:前端框架:layuimini,我已经默认你清楚RCBA模型了。用户角色资源,加两个中间表也就是五张表,还有一些特殊情况,我补充了一张表,用户资源关系表。所以我数据库底层用了6张表维护我的权限模块。

1、以前让我烦呐init.json
layuimini在index页面,是读取init.json获取菜单信息的,这是死数据,而我们要让不同用户有不同菜单,就必须自己构造出这样的init.json.
@GetMapping("/getMenuInfo")
@ApiOperation("获取用户菜单栏")
public Map<String,Object> getMenuInfo(Principal principal){
log.info("===系统正在获取用户菜单栏中====");
Map<String,Object> resultMap = new LinkedHashMap<>();
Map<String,Object> homeInfo = new LinkedHashMap<>();
Map<String,Object> logoInfo = new LinkedHashMap<>();
homeInfo.put("title","首页");
homeInfo.put("href","/welcome");
logoInfo.put("title","Repository");
logoInfo.put("image","static/images/logo.png");
logoInfo.put("href","");
resultMap.put("homeInfo",homeInfo);
resultMap.put("logoInfo",logoInfo);
UmsAdmin admin = adminService.getAdminByUsername(principal.getName());
List<MenuNode> menuNodeList = adminService.getMenuInfo(admin.getId());
resultMap.put("menuInfo",menuNodeList);
log.info("===系统成功获取用户菜单栏=====");
return resultMap;
}
2、SpringSecurity的用户详情
在SpringSecurity中都是基于用户名的认证,他提供了UserDetail接口,让我们可以扩展自己的UserDetail.同时也开发或UserDetailService业务处理类,里头的方法就是根据用户名获取用户详情。所以只要把获取用户详情和自己的数据库产生联系就可以了。
2.1、我的用户详情
package com.wnx.mall.security;
import cn.hutool.core.util.StrUtil;
import com.wnx.mall.mbg.model.UmsAdmin;
import com.wnx.mall.mbg.model.UmsPermission;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
/**
* SpringSecurity需要的用户详情
*/
public class AdminUserDetails implements UserDetails {
/**
* 系统用户
*/
private UmsAdmin umsAdmin;
/**
* 用户权限
*/
private List<UmsPermission> permissionList;
public AdminUserDetails(UmsAdmin umsAdmin, List<UmsPermission> permissionList) {
this.umsAdmin = umsAdmin;
this.permissionList = permissionList;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
//返回当前用户的权限
return permissionList.stream()
.filter(permission -> StrUtil.isNotBlank(permission.getValue()))
.map(permission ->new SimpleGrantedAuthority(permission.getValue()))
.collect(Collectors.toList());
}
@Override
public String getPassword() {
return umsAdmin.getPassword();
}
@Override
public String getUsername() {
return umsAdmin.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return umsAdmin.getStatus().equals(1);
}
}
2.2、我的处理用户详情的业务类
@Bean
@Override
public UserDetailsService userDetailsService() {
//获取登录用户信息
return username -> {
UmsAdmin admin = adminService.getAdminByUsername(username); //联系~!!~~
if (admin != null) {
List<UmsPermission> permissionList = adminService.getPermissionList(admin.getId()); //联系~!!~~
String permissionStr = permissionList.stream()
.filter(p -> p.getValue() != null)
.map(UmsPermission::getValue)
.collect(Collectors.joining(","));
log.info(username+"用户正在从数据库成功读取到权限{}",permissionStr);
return new AdminUserDetails(admin,permissionList); //看上面构造方法
}
throw new UsernameNotFoundException("用户名或密码错误");
};
}
2.3、JWT
用户登录成功后,我返回了JWT,并保存到客户端的cookies中!因为请求的无状态性,为了鉴别请求是谁发起,我的JWT过滤器,就处理的这个问题。有这个JWT的cookies,就放行,没有非法请求,打回去,拒绝访问。
package com.wnx.mall.security;
import com.wnx.mall.common.utils.JwtTokenUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* JWT登录授权过滤器
*
* @author macro
* @date 2018/4/26
*/
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Value("${jwt.tokenHeader}")
private String tokenHeader;
@Value("${jwt.tokenHead}")
private String tokenHead;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String authHeader = null;
//从cookies中取出jwt
Cookie[] cookies = request.getCookies();
if (cookies!=null && cookies.length>0){
for (Cookie cookie : cookies) {
if ("token".equals(cookie.getName())){
authHeader = cookie.getValue();
}
}
}
if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
String authToken = authHeader.substring(this.tokenHead.length());
String username = jwtTokenUtil.getUserNameFromToken(authToken);
LOGGER.info("系统正在检测该用户===》:{}", username);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
LOGGER.info("认证通过,当前认证用户===》:{}", username);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
chain.doFilter(request, response);
}
}
3、用户登录调用的接口
@ApiOperation("登录以后返回token")
@PostMapping(value = "/login")
public CommonResult login(@RequestBody @Validated UmsAdminLoginParam umsAdminLoginParam) {
log.info("=========系统正在进行用户登录=======");
String token = adminService.login(umsAdminLoginParam.getUsername(), umsAdminLoginParam.getPassword());
if (token == null) { return CommonResult.validateFailed("用户名或密码错误"); }
Map<String, String> tokenMap = new HashMap<>(2);
tokenMap.put("token", token);
tokenMap.put("tokenHead", tokenHead);
log.info("=========系统登录成功,返回token{}=======",tokenMap);
return CommonResult.success(tokenMap);
}
/**
* 登录功能
* @param username 用户名
* @param password 密码
* @return
*/
@Override
public String login(String username, String password) {
String token = null;
try {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (!passwordEncoder.matches(password, userDetails.getPassword())) { throw new BadCredentialsException("密码不正确"); }
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
token = jwtTokenUtil.generateToken(userDetails);
} catch (AuthenticationException e) {
log.warn("登录异常:{}", e.getMessage());
}
return token;
}
4、怎么拦截?
拦截就要在配置类中配置了。配置哪些请求是放行的,哪些请求需要认证。意思: 除了上面放行的请求,其他都必须认证。
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.GET,
"/",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/static/**",
"/swagger-resources/**",
"/v2/api-docs/**"
)
.permitAll()
.antMatchers("/admin/login", "/admin/register")
.permitAll()
.antMatchers(HttpMethod.OPTIONS)
.permitAll()
.anyRequest()
.authenticated();
httpSecurity.formLogin().loginPage("/toLoginPage");
httpSecurity.logout().logoutSuccessUrl("/");
//开启对frame框架的支持
httpSecurity.headers().frameOptions().disable();
// 禁用缓存
httpSecurity.headers().cacheControl();
// 添加JWT filter
httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
//添加自定义未授权和未登录结果返回
httpSecurity.exceptionHandling()
.accessDeniedHandler(restfulAccessDeniedHandler)
.authenticationEntryPoint(restAuthenticationEntryPoint);
}
5、源码
可见仓库管理系统
权限-说说SpringSecurity
想想以前为了赶课设的自己,当时用的Shiro,现在用SpringSecurity去探索了一番。以Layui作为前端为例子:前端框架:layuimini,我已经默认你清楚RCBA模型了。用户角色资源,加两个中间表也就是五张表,还有一些特殊情况,我补充了一张表,用户资源关系表。所以我数据库底层用了6张表维护我的权限模块。

1、以前让我烦呐init.json
layuimini在index页面,是读取init.json获取菜单信息的,这是死数据,而我们要让不同用户有不同菜单,就必须自己构造出这样的init.json.
@GetMapping("/getMenuInfo")
@ApiOperation("获取用户菜单栏")
public Map<String,Object> getMenuInfo(Principal principal){
log.info("===系统正在获取用户菜单栏中====");
Map<String,Object> resultMap = new LinkedHashMap<>();
Map<String,Object> homeInfo = new LinkedHashMap<>();
Map<String,Object> logoInfo = new LinkedHashMap<>();
homeInfo.put("title","首页");
homeInfo.put("href","/welcome");
logoInfo.put("title","Repository");
logoInfo.put("image","static/images/logo.png");
logoInfo.put("href","");
resultMap.put("homeInfo",homeInfo);
resultMap.put("logoInfo",logoInfo);
UmsAdmin admin = adminService.getAdminByUsername(principal.getName());
List<MenuNode> menuNodeList = adminService.getMenuInfo(admin.getId());
resultMap.put("menuInfo",menuNodeList);
log.info("===系统成功获取用户菜单栏=====");
return resultMap;
}
2、SpringSecurity的用户详情
在SpringSecurity中都是基于用户名的认证,他提供了UserDetail接口,让我们可以扩展自己的UserDetail.同时也开发或UserDetailService业务处理类,里头的方法就是根据用户名获取用户详情。所以只要把获取用户详情和自己的数据库产生联系就可以了。
2.1、我的用户详情
package com.wnx.mall.security;
import cn.hutool.core.util.StrUtil;
import com.wnx.mall.mbg.model.UmsAdmin;
import com.wnx.mall.mbg.model.UmsPermission;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
/**
* SpringSecurity需要的用户详情
*/
public class AdminUserDetails implements UserDetails {
/**
* 系统用户
*/
private UmsAdmin umsAdmin;
/**
* 用户权限
*/
private List<UmsPermission> permissionList;
public AdminUserDetails(UmsAdmin umsAdmin, List<UmsPermission> permissionList) {
this.umsAdmin = umsAdmin;
this.permissionList = permissionList;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
//返回当前用户的权限
return permissionList.stream()
.filter(permission -> StrUtil.isNotBlank(permission.getValue()))
.map(permission ->new SimpleGrantedAuthority(permission.getValue()))
.collect(Collectors.toList());
}
@Override
public String getPassword() {
return umsAdmin.getPassword();
}
@Override
public String getUsername() {
return umsAdmin.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return umsAdmin.getStatus().equals(1);
}
}
2.2、我的处理用户详情的业务类
@Bean
@Override
public UserDetailsService userDetailsService() {
//获取登录用户信息
return username -> {
UmsAdmin admin = adminService.getAdminByUsername(username); //联系~!!~~
if (admin != null) {
List<UmsPermission> permissionList = adminService.getPermissionList(admin.getId()); //联系~!!~~
String permissionStr = permissionList.stream()
.filter(p -> p.getValue() != null)
.map(UmsPermission::getValue)
.collect(Collectors.joining(","));
log.info(username+"用户正在从数据库成功读取到权限{}",permissionStr);
return new AdminUserDetails(admin,permissionList); //看上面构造方法
}
throw new UsernameNotFoundException("用户名或密码错误");
};
}
2.3、JWT
用户登录成功后,我返回了JWT,并保存到客户端的cookies中!因为请求的无状态性,为了鉴别请求是谁发起,我的JWT过滤器,就处理的这个问题。有这个JWT的cookies,就放行,没有非法请求,打回去,拒绝访问。
package com.wnx.mall.security;
import com.wnx.mall.common.utils.JwtTokenUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* JWT登录授权过滤器
*
* @author macro
* @date 2018/4/26
*/
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Value("${jwt.tokenHeader}")
private String tokenHeader;
@Value("${jwt.tokenHead}")
private String tokenHead;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String authHeader = null;
//从cookies中取出jwt
Cookie[] cookies = request.getCookies();
if (cookies!=null && cookies.length>0){
for (Cookie cookie : cookies) {
if ("token".equals(cookie.getName())){
authHeader = cookie.getValue();
}
}
}
if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
String authToken = authHeader.substring(this.tokenHead.length());
String username = jwtTokenUtil.getUserNameFromToken(authToken);
LOGGER.info("系统正在检测该用户===》:{}", username);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
LOGGER.info("认证通过,当前认证用户===》:{}", username);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
chain.doFilter(request, response);
}
}
3、用户登录调用的接口
@ApiOperation("登录以后返回token")
@PostMapping(value = "/login")
public CommonResult login(@RequestBody @Validated UmsAdminLoginParam umsAdminLoginParam) {
log.info("=========系统正在进行用户登录=======");
String token = adminService.login(umsAdminLoginParam.getUsername(), umsAdminLoginParam.getPassword());
if (token == null) { return CommonResult.validateFailed("用户名或密码错误"); }
Map<String, String> tokenMap = new HashMap<>(2);
tokenMap.put("token", token);
tokenMap.put("tokenHead", tokenHead);
log.info("=========系统登录成功,返回token{}=======",tokenMap);
return CommonResult.success(tokenMap);
}
/**
* 登录功能
* @param username 用户名
* @param password 密码
* @return
*/
@Override
public String login(String username, String password) {
String token = null;
try {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (!passwordEncoder.matches(password, userDetails.getPassword())) { throw new BadCredentialsException("密码不正确"); }
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
token = jwtTokenUtil.generateToken(userDetails);
} catch (AuthenticationException e) {
log.warn("登录异常:{}", e.getMessage());
}
return token;
}
4、怎么拦截?
拦截就要在配置类中配置了。配置哪些请求是放行的,哪些请求需要认证。意思: 除了上面放行的请求,其他都必须认证。
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.GET,
"/",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/static/**",
"/swagger-resources/**",
"/v2/api-docs/**"
)
.permitAll()
.antMatchers("/admin/login", "/admin/register")
.permitAll()
.antMatchers(HttpMethod.OPTIONS)
.permitAll()
.anyRequest()
.authenticated();
httpSecurity.formLogin().loginPage("/toLoginPage");
httpSecurity.logout().logoutSuccessUrl("/");
//开启对frame框架的支持
httpSecurity.headers().frameOptions().disable();
// 禁用缓存
httpSecurity.headers().cacheControl();
// 添加JWT filter
httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
//添加自定义未授权和未登录结果返回
httpSecurity.exceptionHandling()
.accessDeniedHandler(restfulAccessDeniedHandler)
.authenticationEntryPoint(restAuthenticationEntryPoint);
}
5、源码
可见仓库管理系统