背景:最近在搭建这玩意儿,本来就毕设那会用了一下,还是我自己全栈,也不需要前后端分离,但是现在不行了,前后端分离了。而且这边还有账号登录验证和短信验证码验证,草鸡累了。
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/teach/common/*").permitAll().anyRequest().authenticated()
.antMatchers("/teach/courseware/*").hasAuthority("teacher")
// .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().addFilter(new JWTAuthenticationFilter(authenticationManager())).
addFilter(new JWTAuthorizationFilter(authenticationManager()))
.addFilter(new JWTSAMAuthenticationFilter(authenticationManager()))
.exceptionHandling().authenticationEntryPoint(new JwtAuthenticationEntryPoint())
.accessDeniedHandler(new jwtAccessDeniedHandler())
.and().logout()
.and().csrf().disable();
http.cors().disable();
}
首先这是重写WebSecurityConfigurerAdapterd,来加入新加的JWT用户名登录和短信验证码登录的登录成功生成token的过滤器和一个鉴权的过滤器。
MyDetailService
@Component
public class MyUserDetailService implements UserDetailsService {
@Autowired
UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (null==username || "".equals(username)){
return null;
}
UserDTO userDTO = userService.findUserInfoByUsername(username);
if (null==userDTO){
return null;
}
List<GrantedAuthority> authorities=new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority(userDTO.getRole()));
return new UserInfo(userDTO.getId(),userDTO.getUsername(),userDTO.getPassword(),authorities);
}
}
自定义了一个detailservice,做这个第一当然是从数据库中动态验证用户信息,第二用户的id也封装进去了,毕竟后期真的写代码,还是用户id用的比较广泛。
UserInfo
package com.ccas.common.entity;
import io.swagger.models.auth.In;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.util.Collection;
import java.util.List;
public class UserInfo extends User {
private Integer id;
private String username;
private String password;
private String role;
public UserInfo(Integer id,String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
this.id=id;
}
public UserInfo(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
public Integer getId() {
return id;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
}
这边重写了Spring security的user类,毕竟从上可以看到原始的这个类只有用户,密码和权限组,这边把id和单个角色放进去了。
JWTAuthenticationFilter
package com.ccas.common.jwt;
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
@Autowired
UserService userService;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
setAuthenticationManager(authenticationManager);
this.authenticationManager = authenticationManager;
super.setFilterProcessesUrl("/teach/common/login");
System.out.println("filter==="+authenticationManager+"---");
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
// 从输入流中获取到登录的信息
try {
JSONObject jsonObject = new JSONObject(); // 前端 发送 json 对象 username password
JWTUser loginUser = jsonObject.parseObject(request.getInputStream(), JWTUser.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword())
);
} catch (Exception e) {
try {
Result info = new Result();
info.setCode(500);
info.setMessage("账号或密码认证失败");
response.setContentType("text/json;charset=utf-8");
String s = JSONObject.toJSONString(info);
response.getWriter().print(s);
} catch (IOException e1) {
e1.printStackTrace();
}
return null;
}
}
// 成功验证后调用的方法
// 如果验证成功,就生成token并返回
@Override
public void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
// 认证成功 我们需要将用户信息 生成token 发送给客户端
UserInfo userInfo = (UserInfo) authResult.getPrincipal();
Collection<GrantedAuthority> authorities = userInfo.getAuthorities();
GrantedAuthority[] objects = authorities.toArray(new GrantedAuthority[]{});
StringBuffer sb = new StringBuffer();
for (int i=0;i<authorities.size();i++){
String authority = objects[i].getAuthority();
if(i==authorities.size()-1){
sb.append(authority) ;
}else{
sb.append(authority+"-") ;
}
}
userInfo.setRole(sb.toString());
String token = null;
response.setContentType("application/json; charset=utf-8");
Result<Object> errorInfoDTO = new Result();
try {
token = JwtUtils.generateToken(userInfo);
String tokenStr = JwtUtils.TOKEN_PREFIX+ token;
Map<String,Object> map=new HashMap<>();
map.put("token",tokenStr);
map.put("role",sb.toString());
response.setHeader(JwtUtils.TOKEN_NAME,tokenStr); // header形式 发送给客户端浏览器
errorInfoDTO.setCode(200);
errorInfoDTO.setMessage("登录成功");
errorInfoDTO.setData(map);
} catch (Exception e) {
e.printStackTrace();
errorInfoDTO.setCode(500);
errorInfoDTO.setMessage("token生成失败");
}
String s = JSONObject.toJSONString(errorInfoDTO);
response.getWriter().print(s);
}
@Override
public void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
response.setStatus(403);
response.setContentType("application/json; charset=utf-8");
if (failed instanceof BadCredentialsException) {
response.getWriter().write("authentication failed, reason: 密码错误" );
}else{
response.getWriter().write("authentication failed, reason: " + failed.getMessage());
}
}
}
这是用户名密码验证。默认的登录的url是login,所以这边重新定义了一个新的登录url。从前端发送过来的json对象,用一个user对象去接收到,并放入默认的UsernamePasswordAuthenticationToken去进行密码校验,要是成功登录就通过JWTUtils去生成相对应的token。
public static String generateToken(UserInfo userInfo) throws Exception {
return Jwts.builder()
.claim(JwtConstants.JWT_KEY_ID, userInfo.getId())
.claim(JwtConstants.JWT_KEY_USER_NAME, userInfo.getUsername())
.claim(JwtConstants.JWT_KEY_USER_ROLE, userInfo.getRole())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRITION))
.signWith(SignatureAlgorithm.HS256, APPSECRET_KEY)
.compact();
}
接下来就是鉴权了。
package com.ccas.common.jwt;
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
public JWTAuthorizationFilter(AuthenticationManager authenticationManager ) {
super(authenticationManager);
}
@Override
public void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
String tokenHeader = request.getHeader(JwtUtils.TOKEN_NAME);
// 如果请求头中没有Authorization信息则直接放行了
if (tokenHeader == null || !tokenHeader.startsWith(JwtUtils.TOKEN_PREFIX)) {
chain.doFilter(request, response);
}else{
SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader,response));
super.doFilterInternal(request, response, chain);
}
}
private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader,HttpServletResponse response) throws IOException {
String token = tokenHeader.replace(JwtUtils.TOKEN_PREFIX, "");
ServletOutputStream out = response.getOutputStream();
Result errorInfoDTO = new Result();
try {
Map<String, String> infoFromToken = JwtUtils.getInfoFromToken(token);
String userRole = infoFromToken.get("role");
String userName = infoFromToken.get("username");
// 根据token 获取 用户信息 封装到UsernamePasswordAuthenticationToken对象中
if (userName != null) {
String[] roles1 = userRole.split("-");
List<String> list = Arrays.asList(roles1);
List<GrantedAuthority> listRoles = new ArrayList<GrantedAuthority>();
for (String s : list) {
listRoles.add(new SimpleGrantedAuthority(s));
}
return new UsernamePasswordAuthenticationToken(infoFromToken, null, listRoles);
}
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
errorInfoDTO.setCode(500);
errorInfoDTO.setMessage("认证解析失败");
String s = JSONObject.toJSONString(errorInfoDTO);
out.write(s.getBytes());
out.flush();
out.close();
}
return null;
}
}
public static Map<String,String> getInfoFromToken(String token) throws Exception {
Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
Map<String,String> map=new HashMap<>();
map.put("id",claims.get("id").toString());
map.put("username",claims.get("username").toString());
map.put("role",claims.get("role").toString());
return map;
}
鉴权就是登录成功之后,前端访问别的请求时候,就会自动去鉴权,这时候每次访问就会头部带有一个token的,所以每次请求,这边就会去看头部是不是有个token,要是没有携带token或者token开头没有带最开始装配的前缀就放行直接出个异常。
如果正确携带了token,就开始往下解析token,正常解析之后将返回的UsernamePasswordAuthenticationToken放入Spring security的上下文之中。要是权限不足,内部就会报一个权限不够的异常,这里我只能在全局捕捉那边将其显示出来。
@GetMapping("/findMenuWare")
@PreAuthorize("hasAuthority('teacher')")
public Result<Object> findMenuWare(){
Map<String,String> userInfo = (Map<String, String>) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Integer userInfoId= Integer.parseInt(userInfo.get("id"));
List<Tree<String>> findMenuWare = menuService.findmenuware(userInfoId);
return new Result<>(StatusCode.SUCCESS,"查询成功", findMenuWare);
}
在鉴权的时候用用户信息map放入Spring security上下文里面,所以这边可以直接拿出来使用用户id。
这些是用户名密码登录。短信的话,就是在前端请求验证码的时候,将验证码放入session之中。
package com.ccas.common.jwt;
public class JWTSAMAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public JWTSAMAuthenticationFilter(AuthenticationManager authenticationManager) {
setAuthenticationManager(authenticationManager);
this.authenticationManager = authenticationManager;
super.setFilterProcessesUrl("/teach/common/smslogin");
System.out.println("filter==="+authenticationManager+"---");
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
// 从输入流中获取到登录的信息
try {
JSONObject jsonObject = new JSONObject(); // 前端 发送 json 对象 username password
JWTUser loginUser = jsonObject.parseObject(request.getInputStream(), JWTUser.class);
String code = (String) request.getSession().getAttribute("code");
if (code.equals(loginUser.getCode())){
loginUser.setPassword("www.caoji.com");
loginUser.setUsername(loginUser.getPhone());
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword())
);
}else {
Result info = new Result();
info.setCode(500);
info.setMessage("短信验证码错误");
response.setContentType("text/json;charset=utf-8");
String s = JSONObject.toJSONString(info);
response.getWriter().print(s);
return null;
}
} catch (Exception e) {
try {
Result info = new Result();
info.setCode(500);
info.setMessage("账号或密码认证失败");
response.setContentType("text/json;charset=utf-8");
String s = JSONObject.toJSONString(info);
response.getWriter().print(s);
} catch (IOException e1) {
e1.printStackTrace();
}
return null;
}
}
// 成功验证后调用的方法
// 如果验证成功,就生成token并返回
@Override
public void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
// 认证成功 我们需要将用户信息 生成token 发送给客户端
UserInfo userInfo = (UserInfo) authResult.getPrincipal();
Collection<GrantedAuthority> authorities = userInfo.getAuthorities();
GrantedAuthority[] objects = authorities.toArray(new GrantedAuthority[]{});
StringBuffer sb = new StringBuffer();
for (int i=0;i<authorities.size();i++){
String authority = objects[i].getAuthority();
if(i==authorities.size()-1){
sb.append(authority) ;
}else{
sb.append(authority+"-") ;
}
}
userInfo.setRole(sb.toString());
String token = null;
response.setContentType("application/json; charset=utf-8");
Result<Object> errorInfoDTO = new Result();
try {
token = JwtUtils.generateToken(userInfo);
String tokenStr = JwtUtils.TOKEN_PREFIX+ token;
Map<String,Object> map=new HashMap<>();
map.put("token",tokenStr);
map.put("role",sb.toString());
response.setHeader(JwtUtils.TOKEN_NAME,tokenStr); // header形式 发送给客户端浏览器
errorInfoDTO.setCode(200);
errorInfoDTO.setMessage("登录成功");
errorInfoDTO.setData(map);
} catch (Exception e) {
e.printStackTrace();
errorInfoDTO.setCode(500);
errorInfoDTO.setMessage("token生成失败");
}
String s = JSONObject.toJSONString(errorInfoDTO);
response.getWriter().print(s);
}
@Override
public void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
response.setStatus(403);
response.setContentType("application/json; charset=utf-8");
if (failed instanceof BadCredentialsException) {
response.getWriter().write("authentication failed, reason: 密码错误" );
}else{
response.getWriter().write("authentication failed, reason: " + failed.getMessage());
}
}
}
@SuppressWarnings("deprecation")
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
if ((!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) && !presentedPassword.equals("www.caoji.com")) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"密码错误"));
}
}
jWTUser就是把上面用户名密码和这边的手机号验证码封装了一个json对象了,想要好接受来着。改了一个密码校验的源码,要是正确密码或者是我自定义的"www.caoji.com"都算验证通过。前端传来的code和之前存在session之中的code进行校验,要是一致,手动把密码设置为我的caoji自定义密码,反正这样也是可以走得通。
就这样短信验证也是可以了。
做这个的时候,但是Springsecurity好变态,各种重写简直了。但是我还是想说pro真的好香。那个显示栏,我爱了。争取走上苹果六件套!嘻嘻