之前写过一篇的,但是感觉有点垃圾,这次重新重写了一遍。细节不说了.
首先看一下请求方法
@Autowired
LoginService loginService;
@PostMapping("/login")
public Result login(@RequestBody LoginUser loginUser){
String token = loginService.login(loginUser);
return Result.ok(token,"登录成功");
}
@GetMapping("/t1")
@PreAuthorize("hasAuthority('sys:user:edit1')")
public Result t1(){
return Result.ok("没有权限的");
}
@GetMapping("/t2")
@PreAuthorize("hasAuthority('sys:user:del')")
public Result t2(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = authentication.getPrincipal();
return Result.ok(principal,"有权限的2");
}
@GetMapping("/noPer")
public String noPer(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
System.err.println(authentication);
return "不需要权限的";
}
效果图
下面直接上代码
登录实体类
@Data
public class LoginUser implements Serializable{
private static final long serialVersionUID = 1L;
private String username;//用户名
private String password;//密码
private String token;//登录认证
private Long loginTime;//登录时间
private Long expireTime;//过期时间
//private SysUserEntity sysUserEntity;
private List<String> permsList;//权限集合
}
登录方法
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;
import nyist.com.project.entity.LoginUser;
import nyist.com.project.utils.RedisUtil;
@Component
public class LoginService {
@Autowired
RedisUtil redisUtil;
@Resource
private AuthenticationManager authenticationManager;
public String login(LoginUser loginUser) {
String username = loginUser.getUsername();
try {
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, loginUser.getPassword()));//执行认证方法
} catch (Exception e) {
throw new BadCredentialsException("用户认证错误");
}
//这里模拟查询出权限,放在这里主要是为了避免在userdetailService登录认证错误的时候就把数据权限查出来了,所以放在这里
String[] perms = {"sys:user:del","sys:user:add","sys:user:update"};
List<String> permsList= new ArrayList<>();
for(String perm: perms) {
permsList.add(perm);
}
loginUser.setPermsList(permsList);
String uuid = UUID.randomUUID().toString().replace("-", "");
loginUser.setToken(uuid);
long currentTimeMillis = System.currentTimeMillis();
long e= 30 * 60 * 1000L;//三十分钟
long expireTime = currentTimeMillis+e;
loginUser.setExpireTime(expireTime);
redisUtil.set(uuid, loginUser, 60*30);//redis三十分钟有效期
return uuid;
}
}
自定义的UserDetailsService
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import nyist.com.project.entity.LoginUser;
import nyist.com.project.utils.MD5Util;
/**
* 用户验证
* @author ljw
*
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService{
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
if(StringUtils.isEmpty(username)||!username.equals("ljw")) {
throw new UsernameNotFoundException("用户:" + username + " 不存在");
}
//模拟从数据库查出来用户
LoginUser loginUser = new LoginUser();
loginUser.setUsername(username);
String pwd = "ljw";
String lower = MD5Util.md5Encrypt32Lower(pwd);
//由于设置了密码加密方式,这里查出来的用户密码肯定是加密过的,所以在这里手动处理一下,不然一会认证会失败
return new User(username, lower, new ArrayList<GrantedAuthority>());
}
}
核心配置类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;
import nyist.com.project.utils.MD5Util;
/**
* 核心配置类
* @author ljw
*
*/
@Component
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled=true,prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
UserDetailsService userDetailsService;
@Autowired
TokenFilter tokenFilter;
/**
* 指定资源放行
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
"/static/**",
"/test/**",
"/login",
"/noPer",
"/logout",
"/doc.html",
"/webjars/**",
"/swagger-resources/**",
"/v2/api-docs/**");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new PasswordEncoder() {
@Override
public String encode(CharSequence rawPassword) {
String password = rawPassword+"";
return DigestUtils.md5DigestAsHex(password.getBytes());
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
String password = rawPassword+"";
return MD5Util.md5Encrypt32Lower(password).equals(encodedPassword);
}
});
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception{
httpSecurity
// CSRF禁用,因为不使用session
.csrf().disable()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 过滤请求
.authorizeRequests()
// 对于登录login 验证码captchaImage 允许匿名访问
.antMatchers("/login", "/captchaImage").anonymous()
.antMatchers(
HttpMethod.GET,
"/",
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/profile/**"
).permitAll()
.antMatchers("/common/download**").anonymous()
.antMatchers("/common/download/resource**").anonymous()
.antMatchers("/swagger-ui.html").anonymous()
.antMatchers("/swagger-resources/**").anonymous()
.antMatchers("/webjars/**").anonymous()
.antMatchers("/*/api-docs").anonymous()
.antMatchers("/druid/**").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
//处理跨域
.and()
.cors()
.and()
.csrf().disable();
//httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
// 添加JWT filter
httpSecurity.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
httpSecurity.exceptionHandling().authenticationEntryPoint(new UnauthorizedHandler());//匿名用户访问无权限资源时的异常
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception{
return super.authenticationManagerBean();
}
}
操作拦截过滤器
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import nyist.com.project.entity.LoginUser;
import nyist.com.project.utils.RedisUtil;
/**
* token过滤验证器
* @author ljw
*
*/
@Component
public class TokenFilter extends OncePerRequestFilter{
@Autowired
RedisUtil redisUtil;
@Autowired
HttpServletResponse response;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException{
LoginUser loginUser = getLoginUser(request);
if (loginUser!=null ){
//这一块解决 GrantedAuthority 等参数无法序列化,懒得去找解决方案,所以也就没去自定义实现UserDetails
List<String> permsList = loginUser.getPermsList();
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
permsList.forEach(each->{
authorities.add(new SimpleGrantedAuthority(each));
});
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthList());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
chain.doFilter(request, response);
}
private LoginUser getLoginUser(HttpServletRequest request) throws IOException {
String token = request.getHeader("Authorization");
if(token==null) {
return null;
}
Object object = redisUtil.get(token);
if(object==null) {
return null;
}
LoginUser loginUser = (LoginUser)redisUtil.get(token);
long currentTimeMillis = System.currentTimeMillis();
long e= 30 * 60 * 1000L;//三十分钟
if(currentTimeMillis>=loginUser.getExpireTime()) {
return null;
}
long refresTime= loginUser.getExpireTime()-System.currentTimeMillis();
if(refresTime<=5 * 60 * 1000L) {//有效期小于等于五分钟,刷新token
long expireTime = currentTimeMillis+e;
loginUser.setExpireTime(expireTime);
}
redisUtil.set(token, loginUser, 60*30);//redis token再次刷新值
return loginUser;
}
}
认证失败提示处理
/**
* 未登录认证处理
* @author ljw
*
*/
public class UnauthorizedHandler implements AuthenticationEntryPoint{
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.setCharacterEncoding("utf-8");
response.setContentType("text/javascript;charset=utf-8");
response.getWriter().print(JSONObject.toJSONString(Result.fail("您未登录,请先登录~")));
}
}
全局异常处理
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.client.HttpClientErrorException.Unauthorized;
import nyist.com.project.result.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 定义全局运行异常
* @author ljw 2020年11月2日14:36:07
*
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
private final static Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler. class);
/**
* 未登录
* @param req
* @return
*/
@ExceptionHandler(Unauthorized.class)
public Result NoPerm(Exception e){
return Result.fail("请您先登录"+e.getMessage());
}
/**
* 无权限
* @param e
* @return
*/
@ExceptionHandler(AccessDeniedException.class)
public Result accessDeniedException(Exception e){
return Result.fail("暂无权限:"+e.getMessage());
}
/**
* 全局异常处理
* @param e
* @param req
* @return
*/
@ExceptionHandler(value= RuntimeException.class)
public Result runTimeException(RuntimeException e,HttpServletRequest req){
logger.error("错误请求地址URI [{}]", req.getRequestURI());
return Result.fail(e.getMessage());
}
}
RedisUtil工具类网上一大堆,最后配置一下序列化方式,方便自己查看
至于jwt什么的,可在此基础上扩展,用法差不多