demo git地址:传送门
现在绝大部分都是前后端项目,在硬编码中,跳转到某一页面路径,显然不合适,所以应该是服务端返回JSON,
来告知前端是否登录,是否拥有页面权限,登录是否超时等,前端根据返回JSON约定值,来自由跳转。
AccessDeniedHandlerConfig (无权限)
@Component
public class AccessDeniedHandlerConfig implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
JSONObject result = new JSONObject();
result.put("msg","无权限!");
httpServletResponse.setContentType("application/json;charset=utf-8");
httpServletResponse.getWriter().println(result.toJSONString());
}
}
AuthenticationEntryPointConfig(未登录)
@Component
public class AuthenticationEntryPointConfig implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
JSONObject json = new JSONObject();
json.put("msg","未登录");
httpServletResponse.setContentType("application/json;charset=utf-8");
httpServletResponse.getWriter().write("null");
}
}
AuthenticationFailureHandlerConfig(登录失败)
@Component
public class AuthenticationFailureHandlerConfig implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
JSONObject json = new JSONObject();
json.put("msg","登录失败");
httpServletResponse.setContentType("application/json;charset=utf-8");
httpServletResponse.getWriter().write(json.toJSONString());
}
}
AuthenticationSuccessHandlerConfig(登录成功)
@Component
public class AuthenticationSuccessHandlerConfig implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
JSONObject json = new JSONObject();
json.put("msg","登录成功");
httpServletResponse.setContentType("application/json;charset=utf-8");
httpServletResponse.getWriter().write(json.toJSONString());
}
}
LogoutHandlerConfig(注销配置)
@Component
public class LogoutHandlerConfig implements LogoutHandler {
@Override
public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) {
try {
String url = httpServletRequest.getParameter("redirectUrl");
//实现自定义重定向
httpServletResponse.sendRedirect(url);
}catch (IOException e){
}
}
}
注销路径:localhost:8080/logout
redirectUrl 即为前端传来自定义跳转url地址,否则注销后会自动跳转到security默认地址,如:
LogoutSuccessHandlerConfig (注销成功配置)
@Component
public class LogoutSuccessHandlerConfig implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
JSONObject result = new JSONObject();
result.put("msg","注销!");
httpServletResponse.setContentType("application/json;charset=utf-8");
httpServletResponse.getWriter().write(result.toJSONString());
}
}
SysUser,SysRole,SysUserRole
@Data
public class SysUser implements Serializable {
private String userId;
private String userName;
private String password;
private String updateUserId;
private String createUserId;
private LocalDateTime updateTime;
private LocalDateTime createTime;
}
@Data
public class SysRole implements Serializable {
private String roleId;
private String name;
private Integer state;
private String updateUserId;
private String createUserId;
private LocalDateTime updateTime;
private LocalDateTime createTime;
}
@Data
public class SysUserRole implements Serializable {
private String userRoleId;
private String roleId;
private String userId;
private String updateUserId;
private String createUserId;
private LocalDateTime updateTime;
private LocalDateTime createTime;
}
用户名密码 -> (Authentication(未认证) ->
AuthenticationManager -> AuthenticationProvider ->
UserDetailService -> UserDetails -> Authentication(已认证)通过
创建 SelfUserDetails
@Data
public class SelfUserDetails implements UserDetails, Serializable {
private String username;
private String password;
private Set<? extends GrantedAuthority> authorities;
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
创建SelfUserDetailsService,实现UserDetailsService
@Service
public class SelfUserDetailsService implements UserDetailsService {
@Autowired
SysUserService sysUserService;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
SelfUserDetails userInfo = new SelfUserDetails();
userInfo.setUsername(userName);
SysUser sysUser = sysUserService.getUserByUsername(userName);
if (sysUser == null) {
throw new UsernameNotFoundException("用户不存在!");
}
userInfo.setPassword(sysUser.getPassword());
SysRole sysRole = sysUserService.getUserRoleInfo(sysUser.getUserId());
if (sysRole == null) {
throw new UsernameNotFoundException("用户未分配权限!");
}
if (sysRole.getState() == 0) {
throw new UsernameNotFoundException("用户权限失效!");
}
userInfo.setAuthorities(getAuthorities(sysRole.getName()));
return userInfo;
}
private Set<GrantedAuthority> getAuthorities(String role) {
Set<GrantedAuthority> authoritiesSet = new HashSet();
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(role);
authoritiesSet.add(grantedAuthority);
return authoritiesSet;
}
}
创建SelfAuthenticationProvider 类,实现AuthenticationProvider
public class SelfAuthenticationProvider implements AuthenticationProvider {
@Autowired
SelfUserDetailsService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String userName = (String) authentication.getPrincipal();
String password = (String) authentication.getCredentials();
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String encodePwd = bCryptPasswordEncoder.encode(password);
UserDetails userInfo = userDetailsService.loadUserByUsername(userName);
if (userInfo == null) {
throw new UsernameNotFoundException("用户不存在");
}
if(!userInfo.getPassword().equals(encodePwd)) {
throw new BadCredentialsException("密码错误");
}
return new UsernamePasswordAuthenticationToken(userName, password, userInfo.getAuthorities());
}
@Override
public boolean supports(Class> authentication) {
return UsernamePasswordAuthenticationToken.class.equals(authentication);
}
}
创建 RbacService (role-Based-access control)权限控制,url 权限部分未实现,有兴趣的小伙伴可一起完善。
@Service("rbacService")
public class RbacServiceImpl implements RbacService {
private AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
String userName = (String) authentication.getPrincipal();
//读取当前用户是否拥有 url 权限
//todo 未实现
if (antPathMatcher.match("", request.getRequestURI())) {
return true;
}
return false;
}
}
创建SecurityConfig 配置类,配置核心:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.httpBasic().authenticationEntryPoint(authenticationEntryPoint)
.and()
.authorizeRequests()
.antMatchers("/role/**").hasRole("admin")
// 这就表示 /index这个页面不需要权限认证,所有人都可以访问
/* .antMatchers("/test/**").permitAll()
.antMatchers(HttpMethod.POST,"/user/").hasRole("ADMIN")
.antMatchers(HttpMethod.GET,"/user/").hasRole("USER")
.antMatchers("/role/**").hasRole("admin")
.anyRequest().authenticated()*/
.anyRequest().access("@rbacService.hasPermission(request,authentication)")
//.anyRequest().access("")
// 其他 url 需要身份认证
//.authenticated()
.and()
// login 相关
// 开启登录
.formLogin()
// 登录成功
.successHandler(authenticationSuccessHandler)
// 登录失败
.failureHandler(authenticationFailureHandler)
.permitAll()
.and()
// logout 相关
.logout()
.logoutSuccessHandler(logoutSuccessHandler)
.addLogoutHandler(logoutHandler)
.permitAll().and()
// 记住我相关
.rememberMe()
.rememberMeParameter("remember-me").userDetailsService(selfUserDetailsService)
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(6000);
// 无权访问 JSON 格式的数据
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
}
到现在功能基本可以使用了,只需要完善一下各个状态返回result即可使用
@Autowired
DataSource dataSource;
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(dataSource);
return tokenRepository;
}
config 中 加入
.rememberMe()
.rememberMeParameter("remember-me").userDetailsService(selfUserDetailsService)
.tokenRepository(persistentTokenRepository())
demo git地址:传送门
欢迎大家 review,完善补充,指出不足之处,谢谢。