springboot基于shiro/springsecurity实现自定义登录

编程

shiro

shiro 配置文件

/**

* Shiro配置

*/

@Configuration

@RequiredArgsConstructor

public class ShiroConfig {

private final ShiroService shiroService;

private final SysUserTokenService sysUserTokenService;

private final SysCaptchaService sysCaptchaService;

private final Gson gson;

/**

* 自定义权限管理

*

* @see ShiroConfiguration

* @see DefaultSecurityManager

*/

@Bean("securityManager")

public DefaultWebSecurityManager defaultWebSecurityManager() {

DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();

// realms

ArrayList<Realm> realms = new ArrayList<>();

realms.add(userAuthenticatingRealm());

realms.add(tokenAuthorizingRealm());

defaultWebSecurityManager.setRealms(realms);

DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();

DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();

// disable session

defaultSessionStorageEvaluator.setSessionStorageEnabled(false);

subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);

defaultWebSecurityManager.setSubjectDAO(subjectDAO);

// cache

defaultWebSecurityManager.setCacheManager(new MemoryConstrainedCacheManager());

return defaultWebSecurityManager;

}

@Bean

public Realm userAuthenticatingRealm() {

UserAuthenticatingRealm userAuthenticatingRealm = new UserAuthenticatingRealm();

userAuthenticatingRealm.setShiroService(shiroService);

userAuthenticatingRealm.setSysCaptchaService(sysCaptchaService);

PasswordMatcher passwordMatcher = new PasswordMatcher();

passwordMatcher.setPasswordService(passwordService());

userAuthenticatingRealm.setCredentialsMatcher(passwordMatcher);

return userAuthenticatingRealm;

}

@Bean

public Realm tokenAuthorizingRealm() {

TokenAuthorizingRealm tokenAuthorizingRealm = new TokenAuthorizingRealm();

tokenAuthorizingRealm.setShiroService(shiroService);

return tokenAuthorizingRealm;

}

/**

* @see DefaultFilter

*/

@Bean

public ShiroFilterChainDefinition shiroFilterChainDefinition() {

DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();

chainDefinition.addPathDefinition("/sys/login", "user_authc");

chainDefinition.addPathDefinition("/sys/logout", "anon");

chainDefinition.addPathDefinition("/sys/**", "token_authc");

chainDefinition.addPathDefinition("/manage/**", "anon");

chainDefinition.addPathDefinition("/wx/**", "anon");

chainDefinition.addPathDefinition("/**", "anon");

return chainDefinition;

}

@Bean(name = "user_authc")

public UserFilter userAuthenticatingFilter() {

UserFilter userFilter = new UserFilter();

userFilter.setSysUserTokenService(sysUserTokenService);

userFilter.setGson(gson);

return userFilter;

}

@Bean(name = "token_authc")

public TokenFilter tokenAuthenticatingFilter() {

return new TokenFilter();

}

/**

* 总过滤器

*

* @param securityManager { @link #defaultWebSecurityManager }

* @param shiroFilterChainDefinition { @link #shiroFilterChainDefinition }

*/

@Bean(name = "shiroFilterFactoryBean")

public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager,

ShiroFilterChainDefinition shiroFilterChainDefinition,

Map<String, Filter> filterMap) {

ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();

filterFactoryBean.setSecurityManager(securityManager);

filterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition.getFilterChainMap());

filterFactoryBean.setFilters(filterMap);

return filterFactoryBean;

}

/**

* 非接口使用 cglib,防止额外的 aop 导致 @RequiresPermissions 注解失效

*/

@ConditionalOnMissingBean

@Bean

@DependsOn("lifecycleBeanPostProcessor")

public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {

DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();

// 非接口使用 cglib

proxyCreator.setProxyTargetClass(true);

return proxyCreator;

}

@ConditionalOnMissingBean

@Bean

public PasswordService passwordService() {

return new DefaultPasswordService();

}

}

认证

/**

* AuthenticationToken —— 待认证 token

*/

public class UserAuthentication implements AuthenticationToken {

private static final long serialVersionUID = 1L;

private final Object principal;

private Object credentials;

/**

* 验证码

*/

private String captcha;

private String uuid;

private boolean refresh;

/**

* 需要进行认证

*/

public UserAuthentication(Object principal, Object credentials) {

this.principal = principal;

this.credentials = credentials;

}

/**

* 需要进行认证(附带验证码)

*/

public UserAuthentication(Object principal, Object credentials, String captcha) {

this.principal = principal;

this.credentials = credentials;

this.captcha = captcha;

}

/**

* 需要进行认证(附带验证码)

*/

public UserAuthentication(Object principal, Object credentials, String captcha, String uuid) {

this.principal = principal;

this.credentials = credentials;

this.captcha = captcha;

this.uuid = uuid;

}

/**

* 以旧换新, 无须通过密码校验

*/

public UserAuthentication(Object principal, boolean refresh) {

this.principal = principal;

this.refresh = refresh;

}

@Override

public Object getPrincipal() {

return principal;

}

@Override

public Object getCredentials() {

return credentials;

}

public String getCaptcha() {

return captcha;

}

public String getUuid() {

return uuid;

}

}

filter

/**

* 自定义 AuthenticatingFilter 认证过滤器

* <p>

* 登录认证

*/

@Slf4j

public class UserFilter extends AuthenticatingFilter {

private SysUserTokenService sysUserTokenService;

private Gson gson;

/**

* 返回待 AuthorizingRealm 认证的 AuthenticationToken, 参见 {@link UserAuthenticatingRealm#doGetAuthenticationInfo}

*/

@Override

protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {

HttpServletRequest req = (HttpServletRequest) request;

ServletInputStream inputStream = req.getInputStream();

InputStreamReader reader = new InputStreamReader(inputStream);

JsonObject jsonObject = getGson().fromJson(reader, JsonObject.class);

String principle = jsonObject.get(obtainUsernameParam()).getAsString();

String credentials = jsonObject.get(obtainPasswordParam()).getAsString();

String captcha = jsonObject.get(obtainCaptchaParam()).getAsString();

String uuid = jsonObject.get(obtainUuidParam()).getAsString();

return new UserAuthentication(principle, credentials, captcha, uuid);

}

/**

* 判断是否是登录请求

*/

@Override

public String getLoginUrl() {

return SecurityConstant.loginUrl;

}

/**

* 放行非登录请求(如 logout)

*/

@Override

protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {

return super.isAccessAllowed(request, response, mappedValue);

}

/**

* 拦截登录请求 —> 执行

*/

@Override

protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {

if (isLoginRequest(request, response)) {

if (isLoginSubmission(request, response)) {

super.executeLogin(request, response);

} else {

//可能是登录页,故不禁止访问

return true;

}

}

return false;

}

/**

* 登录失败回调

*/

@SuppressWarnings("Duplicates")

@SneakyThrows

@Override

protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException exp, ServletRequest request, ServletResponse response) {

HttpServletResponse httpServletResponse = (HttpServletResponse) response;

Map<String, Object> map = new HashMap<>();

String errorMsg = "未知异常";

int errorCode = 500;

if (exp instanceof CaptchaInvalidException) {

errorMsg = "验证码填写错误";

} else if (exp instanceof UnknownAccountException) {

errorMsg = "账号不存在";

} else if (exp instanceof CredentialsException) {

errorMsg = "密码错误";

} else if (exp instanceof LockedAccountException) {

errorMsg = "账号已锁定";

} else {

log.error("login fail {}", exp.getMessage());

}

map.put("code", errorCode);

map.put("msg", errorMsg);

map.put("success", false);

httpServletResponse.setContentType("application/json");

httpServletResponse.setStatus(HttpServletResponse.SC_OK);

try {

ObjectMapper mapper = new ObjectMapper();

mapper.writeValue(httpServletResponse.getOutputStream(), map);

} catch (Exception er) {

throw new ServletException();

}

// 尝试重新登录

return true;

}

/**

* 登录成功回调

*/

@SuppressWarnings("Duplicates")

@Override

protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {

HttpServletResponse httpServletResponse = (HttpServletResponse) response;

SysUserEntity userEntity = (SysUserEntity) SecurityUtils.getSubject().getPrincipal();

Result r = getSysUserTokenService().createToken(userEntity.getUserId());

httpServletResponse.setContentType("application/json");

httpServletResponse.setStatus(HttpServletResponse.SC_OK);

try {

ObjectMapper mapper = new ObjectMapper();

mapper.writeValue(httpServletResponse.getOutputStream(), r);

} catch (Exception e) {

throw new ServletException();

}

return true;

}

protected String obtainUsernameParam() {

return "username";

}

protected String obtainPasswordParam() {

return "password";

}

protected String obtainCaptchaParam() {

return "captcha";

}

protected String obtainUuidParam() {

return "uuid";

}

protected boolean isLoginSubmission(ServletRequest request, ServletResponse response) {

return (request instanceof HttpServletRequest) && WebUtils.toHttp(request).getMethod().equalsIgnoreCase(POST_METHOD);

}

public SysUserTokenService getSysUserTokenService() {

Assert.notNull(this.sysUserTokenService, "sysUserTokenService is needed");

return sysUserTokenService;

}

public void setSysUserTokenService(SysUserTokenService sysUserTokenService) {

this.sysUserTokenService = sysUserTokenService;

}

public Gson getGson() {

Assert.notNull(this.gson, "gson is needed");

return gson;

}

public void setGson(Gson gson) {

this.gson = gson;

}

}

AuthenticatingRealm

/**

* 用户登录认证

*/

public class UserAuthenticatingRealm extends AuthenticatingRealm {

private ShiroService shiroService;

private SysCaptchaService sysCaptchaService;

public void setShiroService(ShiroService shiroService) {

this.shiroService = shiroService;

}

public SysCaptchaService getSysCaptchaService() {

Assert.notNull(this.sysCaptchaService, "getSysCaptchaService is needed");

return sysCaptchaService;

}

public void setSysCaptchaService(SysCaptchaService sysCaptchaService) {

this.sysCaptchaService = sysCaptchaService;

}

@Override

public boolean supports(AuthenticationToken token) {

return token instanceof UserAuthentication;

}

/**

* 认证

* <p>

* 从数据库查询真实个人信息

*

* @see org.apache.shiro.realm.AuthenticatingRealm#getAuthenticationInfo

* @see org.apache.shiro.realm.AuthenticatingRealm#assertCredentialsMatch

*/

@Override

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

// 校验验证码

UserAuthentication userAuthentication = (UserAuthentication) authenticationToken;

String captcha = userAuthentication.getCaptcha();

String uuid = userAuthentication.getUuid();

boolean validated = getSysCaptchaService().validate(uuid, captcha);

if (!validated) {

throw new CaptchaInvalidException("验证码无效");

}

String username = (String) authenticationToken.getPrincipal();

SysUserEntity user = shiroService.queryByUsername(username);

if (user == null) {

throw new UnknownAccountException("账号不存在");

} else if (user.getStatus() == 0) {

throw new LockedAccountException("账号已被锁定, 请联系管理员");

}

return new SimpleAuthenticationInfo(user, user.getPassword(), getName());

}

}

AuthenticationException

public class CaptchaInvalidException extends AuthenticationException {

public CaptchaInvalidException(String msg) {

super(msg);

}

}

鉴权与授权

/**

* AuthenticationToken —— 待认证 token

*/

public class XxAuthenticationToken implements AuthenticationToken {

private static final long serialVersionUID = 1L;

private final String token;

public XxAuthenticationToken(String token) {

this.token = token;

}

@Override

public Object getPrincipal() {

return token;

}

@Override

public Object getCredentials() {

return token;

}

public String getToken() {

return token;

}

}

filter

@Slf4j

public class TokenFilter extends AuthenticatingFilter {

protected static final String AUTHORIZATION_HEADER = "token";

@Override

protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {

String token = extractRequestToken(request);

return new XxAuthenticationToken(token);

}

@Override

protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {

// super.isAccessAllowed 基于 session 级别放行,这里进行重写

// or call org.apache.shiro.mgt.DefaultSessionStorageEvaluator.setSessionStorageEnabled to disable the session

return false;

}

@Override

protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {

boolean loggedIn = false;

if (isLoginRequest(request, response)) {

loggedIn = executeLogin(request, response);

}

if (!loggedIn) {

//认证失败,提示 token 无效

Map<String, Object> map = new HashMap<>();

String errorMsg = "invalid token";

int errorCode = 500;

map.put("code", errorCode);

map.put("msg", errorMsg);

map.put("success", false);

HttpServletResponse httpResponse = WebUtils.toHttp(response);

httpResponse.setContentType("application/json");

httpResponse.setStatus(HttpServletResponse.SC_OK);

try {

ObjectMapper mapper = new ObjectMapper();

mapper.writeValue(httpResponse.getOutputStream(), map);

} catch (Exception er) {

throw new ServletException();

}

}

return loggedIn;

}

@Override

protected final boolean isLoginRequest(ServletRequest request, ServletResponse response) {

return StringUtils.isNotBlank(extractRequestToken(request));

}

private String extractRequestToken(ServletRequest request) {

HttpServletRequest httpRequest = WebUtils.toHttp(request);

String token = httpRequest.getHeader(AUTHORIZATION_HEADER);

if (StringUtils.isBlank(token)) {

token = httpRequest.getParameter(AUTHORIZATION_HEADER);

}

return token;

}

}

realm

/**

* 基于 token 的 鉴权与授权

*/

public class TokenAuthorizingRealm extends AuthorizingRealm {

private ShiroService shiroService;

public ShiroService getShiroService() {

Assert.notNull(this.shiroService, "shiroService is needed");

return shiroService;

}

public void setShiroService(ShiroService shiroService) {

this.shiroService = shiroService;

}

@Override

public boolean supports(AuthenticationToken token) {

return token instanceof XxAuthenticationToken;

}

/**

* 认证

*

* 从数据库查询真实个人信息

*

* @see org.apache.shiro.realm.AuthenticatingRealm#getAuthenticationInfo

* @see org.apache.shiro.realm.AuthenticatingRealm#assertCredentialsMatch

*/

@Override

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

String accessToken = (String) authenticationToken.getPrincipal();

SysUserTokenEntity tokenEntity = shiroService.queryByToken(accessToken);

if (tokenEntity == null || tokenEntity.getExpireTime().getTime() < System.currentTimeMillis()) {

throw new IncorrectCredentialsException("token失效,请重新登录");

}

SysUserEntity user = shiroService.queryUser(tokenEntity.getUserId());

if (user.getStatus() == 0) {

throw new LockedAccountException("账号已被锁定,请联系管理员");

}

return new SimpleAuthenticationInfo(user, accessToken, getName());

}

/**

* 授权

*

* 从数据库获取权限信息

*

* @see org.apache.shiro.realm.AuthorizingRealm#isPermitted

*/

@Override

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

SysUserEntity user = (SysUserEntity) principals.getPrimaryPrincipal();

Long userId = user.getUserId();

//用户权限列表

Set<String> permsSet = getShiroService().getUserPermissions(userId);

SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

info.setStringPermissions(permsSet);

return info;

}

}

 

Spring Security

spring security 配置文件

@EnableWebSecurity

@AllArgsConstructor

@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)

public class SecurityConf extends WebSecurityConfigurerAdapter {

private TokenAuthenticationConfig tokenAuthenticationConfig;

private CorsFilter corsFilter;

private IPermissionService permissionService;

@Override

protected void configure(HttpSecurity http) throws Exception {

ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.csrf().disable()

.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)

.exceptionHandling()

.authenticationEntryPoint(new Http401UnAuthEntryPoint())

.accessDeniedHandler(new XxAccessDeniedHandler())

.and()

.headers()

.frameOptions()

.disable()

// create no session

.and()

.sessionManagement()

.sessionCreationPolicy(SessionCreationPolicy.STATELESS)

.and()

.authorizeRequests()

.antMatchers("/doc.htm**", "/service-worker.js",

"/v2/api-docs", "/configuration/ui", "/swagger-resources/**", "/v2/api-docs-ext",

"/configuration/security", "/swagger-ui.html", "/webjars/**",

"/favicon.ico", "/static/**",

"/acc/**", "/third/**", "/public/**"

).permitAll();

registry.antMatchers("/actuator/**")

.hasRole("ADMIN");

plusPermissions(registry);

registry

.anyRequest()

.authenticated()

.and()

.apply(tokenAuthenticationConfig);

}

@Bean

@Override

public AuthenticationManager authenticationManagerBean() throws Exception {

return super.authenticationManagerBean();

}

private void plusPermissions(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {

List<Permission> permissions = permissionService.list();

if (CollectionUtil.isNotEmpty(permissions)) {

permissions.forEach(permission -> {

String urls = permission.getUrls();

if (StringUtil.isNotBlank(urls)) {

String[] urlArr = urls.split(",");

String curPerm = "PERMISSION_" + permission.getCode();

String access = String.format("hasRole("%s") or hasAuthority("%s")", RoleName.ADMIN.getName(), curPerm);

if (urlArr.length > 0) {

registry.antMatchers(urlArr)

.access(access);

}

}

});

}

}

}

认证

public class TokenAuthentication extends AbstractAuthenticationToken {

private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

private final Object principal;

private Object credentials;

/**

* 认证前

*/

public TokenAuthentication(Object principal, Object credentials) {

super(null);

this.principal = principal;

this.credentials = credentials;

setAuthenticated(false);

}

/**

* 认证后

*/

public TokenAuthentication(Object principal, Object credentials,

Collection<? extends GrantedAuthority> authorities) {

super(authorities);

this.principal = principal;

this.credentials = credentials;

super.setAuthenticated(true); // must use super, as we override

}

@Override

public Object getCredentials() {

return this.credentials;

}

@Override

public Object getPrincipal() {

return this.principal;

}

@Override

public void eraseCredentials() {

super.eraseCredentials();

credentials = null;

}

@Override

public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {

if (isAuthenticated) {

throw new IllegalArgumentException(

"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");

}

super.setAuthenticated(false);

}

}

filter

/**

* doFilter 负责预鉴权(验证码,安全码是否一致)

* attemptAuthentication 负责提取 token

*/

@Slf4j

public class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

private TokenService tokenService;

public TokenAuthenticationFilter() {

super(new AntPathRequestMatcher(SecurityConstant.LOGIN_URL, "POST"));

}

public void setTokenService(TokenService tokenService) {

this.tokenService = tokenService;

}

public TokenService getTokenService() {

Assert.notNull(this.tokenService, "tokenService is needed");

return this.tokenService;

}

/**

* 鉴权

* - 路由规则匹配

* - 验证码校验(短信验证码或阿里云人机校验)

*/

@Override

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

HttpServletRequest req = (HttpServletRequest) request;

HttpServletResponse res = (HttpServletResponse) response;

// 判断是否是登录请求 LOGIN_URL

if (!requiresAuthentication(req, res)) {

chain.doFilter(request, response);

return;

}

try {

// 校验参数与验证码

getTokenService().checkParams(req, res);

} catch (AuthenticationException error) {

unsuccessfulAuthentication(req, res, error);

return;

}

super.doFilter(req, res, chain);

}

/**

* 返回一个待 Provider 认证的 token(TokenAuthentication)

*/

@Override

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {

// 拦截非 POST 请求

if (!request.getMethod().equals(HttpMethod.POST.name())) {

throw new AuthenticationServiceException(

"Authentication method not supported: " + request.getMethod());

}

TokenAuthentication tokenAuthentication = new TokenAuthentication(obtainUsername(request), obtainPassword(request));

return this.getAuthenticationManager().authenticate(tokenAuthentication);

}

@Nullable

protected String obtainUsername(HttpServletRequest request) {

return request.getParameter("username");

}

@Nullable

protected String obtainPassword(HttpServletRequest request) {

return request.getParameter("password");

}

}

provider

/**

* 认证, 返回已认证的 token(Authentication)

*/

public class TokenAuthenticationProvider implements AuthenticationProvider {

protected final Log logger = LogFactory.getLog(getClass());

private UserDetailsService userDetailsService;

private PasswordEncoder passwordEncoder;

private BlackTool blackTool;

private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";

private volatile String userNotFoundEncodedPassword;

private UserDetailsChecker preAuthenticationChecks = new TokenAuthenticationProvider.DefaultPreAuthenticationChecks();

private UserCache userCache = new NullUserCache();

public void setUserDetailsService(UserDetailsService userDetailsService) {

this.userDetailsService = userDetailsService;

}

public void setPasswordEncoder(PasswordEncoder passwordEncoder) {

this.passwordEncoder = passwordEncoder;

}

public void setBlackTool(BlackTool blackTool) {

this.blackTool = blackTool;

}

public UserDetailsService getUserDetailsService() {

Assert.notNull(this.userDetailsService, "userDetailsService could not be null");

return userDetailsService;

}

public PasswordEncoder getPasswordEncoder() {

Assert.notNull(this.passwordEncoder, "passwordEncoder could not be null");

return passwordEncoder;

}

public BlackTool getBlackTool() {

Assert.notNull(this.blackTool, "blackTool could not be null");

return blackTool;

}

/**

* i18n 字符串

*/

protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

@Override

public Authentication authenticate(Authentication authentication) throws AuthenticationException {

// 校验 token 的 class 类型

Assert.isInstanceOf(TokenAuthentication.class, authentication,

messages.getMessage(

"SocialAuthenticationProvider.onlySupports",

"Only SocialAuthenticationToken is supported"));

// 通过 username 提取用户信息

String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"

: authentication.getName();

//检查是否在黑名单中

checkBlackList(username);

// 从缓存中获取用户信息

boolean cacheWasUsed = true;

UserDetails user = this.userCache.getUserFromCache(username);

// 缓存中没有,则从数据库中获取用户信息

if (user == null) {

cacheWasUsed = false;

try {

user = this.retrieveUser(username, (TokenAuthentication) authentication);

} catch (UsernameNotFoundException notFound) {

logger.debug("User username "" + username + "" not found");

throw notFound;

}

Assert.notNull(user,

"retrieveUser returned null - a violation of the interface contract");

}

//检查信息是否匹配有效

try {

preAuthenticationChecks.check(user);

// password check

checkPassword(user, (TokenAuthentication) authentication);

} catch (AuthenticationException exception) {

//重新再试一下

if (cacheWasUsed) {

// There was a problem, so try again after checking

// we"re using latest data (i.e. not from the cache)

cacheWasUsed = false;

user = retrieveUser(username,

(TokenAuthentication) authentication);

preAuthenticationChecks.check(user);

checkPassword(user,

(TokenAuthentication) authentication);

} else {

throw exception;

}

}

// 加入缓存

if (!cacheWasUsed) {

this.userCache.putUserInCache(user);

}

TokenAuthentication authenticationToken = new TokenAuthentication(user, null, user.getAuthorities());

authenticationToken.setDetails(user);

return authenticationToken;

}

/**

* 过滤该 provider 支持认证的 authentication

*/

@Override

public boolean supports(Class<?> authentication) {

return TokenAuthentication.class.isAssignableFrom(authentication);

}

private class DefaultPreAuthenticationChecks implements UserDetailsChecker {

public void check(UserDetails user) {

if (!user.isAccountNonLocked()) {

logger.debug("User account is locked");

throw new LockedException(messages.getMessage(

"AbstractUserDetailsAuthenticationProvider.locked",

"User account is locked"));

}

if (!user.isEnabled()) {

logger.debug("User account is disabled");

throw new DisabledException(messages.getMessage(

"AbstractUserDetailsAuthenticationProvider.disabled",

"User is disabled"));

}

if (!user.isAccountNonExpired()) {

logger.debug("User account is expired");

throw new AccountExpiredException(messages.getMessage(

"AbstractUserDetailsAuthenticationProvider.expired",

"User account has expired"));

}

}

}

public void checkBlackList(String blackKey) {

if (getBlackTool().existInBlackList(blackKey)) {

// 在黑名单中,拒绝访问

throw new AuthFrequentFailException("您已连续5次密码输入错误,请15分钟后再试");

}

}

/**

* 连续多次输入错误,直接禁止登录

*/

public void checkPassword(UserDetails userDetails,

TokenAuthentication authentication) throws AuthenticationException {

if (authentication.getCredentials() == null) {

logger.debug("Authentication failed: no credentials provided");

throw new BadCredentialsException(messages.getMessage(

"AbstractUserDetailsAuthenticationProvider.badCredentials",

"Bad credentials"));

}

String blackKey = authentication.getPrincipal().toString();

String presentedPassword = authentication.getCredentials().toString();

if (!getPasswordEncoder().matches(presentedPassword, userDetails.getPassword())) {

logger.debug("Authentication failed: password does not match stored value");

// 黑名单计数

getBlackTool().incr(blackKey);

throw new BadCredentialsException(messages.getMessage(

"AbstractUserDetailsAuthenticationProvider.badCredentials",

"Bad credentials"));

}

// 黑名单重置

getBlackTool().reset(blackKey);

}

protected final UserDetails retrieveUser(String username,

TokenAuthentication authentication)

throws AuthenticationException {

prepareTimingAttackProtection();

try {

UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);

if (loadedUser == null) {

throw new InternalAuthenticationServiceException(

"UserDetailsService returned null, which is an interface contract violation");

}

return loadedUser;

} catch (UsernameNotFoundException ex) {

mitigateAgainstTimingAttack(authentication);

throw ex;

} catch (InternalAuthenticationServiceException ex) {

throw ex;

} catch (Exception ex) {

throw new InternalAuthenticationServiceException(ex.getMessage(), ex);

}

}

private void prepareTimingAttackProtection() {

if (this.userNotFoundEncodedPassword == null) {

this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);

}

}

private void mitigateAgainstTimingAttack(TokenAuthentication authentication) {

if (authentication.getCredentials() != null) {

String presentedPassword = authentication.getCredentials().toString();

this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);

}

}

}

TokenAuthenticationConfig

@Component

@AllArgsConstructor

public class TokenAuthenticationConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

private TokenService tokenService;

private PasswordEncoder passwordEncoder;

private UserDetailsService userDetailsService;

private BlackTool blackTool;

@Override

public void configure(HttpSecurity http) {

// check filter

TokenCheckFilter checkFilter = new TokenCheckFilter(tokenService);

// handler

TokenFailHandler failHandler = new TokenFailHandler();

TokenSuccessHandler successHandler = new TokenSuccessHandler(tokenService);

// authentication filter

TokenAuthenticationFilter tokenFilter = new TokenAuthenticationFilter();

tokenFilter.setTokenService(tokenService);

tokenFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));

tokenFilter.setAuthenticationSuccessHandler(successHandler);

tokenFilter.setAuthenticationFailureHandler(failHandler);

// provider

TokenAuthenticationProvider tokenProvider = new TokenAuthenticationProvider();

tokenProvider.setPasswordEncoder(passwordEncoder);

tokenProvider.setUserDetailsService(userDetailsService);

tokenProvider.setBlackTool(blackTool);

http.authenticationProvider(tokenProvider)

.addFilterBefore(checkFilter, UsernamePasswordAuthenticationFilter.class)

.addFilterAfter(tokenFilter, UsernamePasswordAuthenticationFilter.class);

}

}

401

public class Http401UnAuthEntryPoint implements AuthenticationEntryPoint {

@Override

public void commence(HttpServletRequest request,

HttpServletResponse response,

AuthenticationException authException) throws IOException {

// This is invoked when user tries to access a secured REST resource without supplying any credentials

// We should just send a 401 Unauthorized response because there is no "login page" to redirect to

// Here you can place any message you want

response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());

}

}

403

@Slf4j

public class XxAccessDeniedHandler implements AccessDeniedHandler {

@Override

public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {

String requestURI = httpServletRequest.getRequestURI();

log.error("access {} wad denied.", requestURI, e);

httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);

httpServletResponse.getWriter().write("access denied");

}

}

fail handler

@Slf4j

public class TokenFailHandler implements AuthenticationFailureHandler {

@Override

public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException exp) throws IOException, ServletException {

Map<String, Object> map = new HashMap<>();

String errorMsg = "未知异常";

int errorCode = 400;

if (exp instanceof UsernameNotFoundException) {

errorMsg = "账号不存在";

} else if (exp instanceof BadCredentialsException) {

errorMsg = "账号或密码错误";

errorCode = 401;

} else if (exp instanceof AuthParamFormatException) {

errorMsg = exp.getMessage();

} else if (exp instanceof AuthEmptyException) {

errorMsg = exp.getMessage();

} else if (exp instanceof AuthFrequentFailException) {

errorMsg = exp.getMessage();

errorCode = 403;

} else if(exp instanceof AuthAfsFailException) {

errorMsg = exp.getMessage();

errorCode = AfsConstant.FAIL_CODE;

} else if (exp instanceof DisabledException) {

errorMsg = "账号已被禁用";

} else if (exp instanceof AccountExpiredException) {

errorMsg = "账号过期";

} else if (exp instanceof LockedException) {

errorMsg = "该账号已被锁定";

} else if (exp instanceof InsufficientAuthenticationException) {

errorMsg = "验证失败";

} else{

log.error("auth error", exp);

}

map.put("code", errorCode);

map.put("msg", errorMsg);

map.put("success", false);

httpServletResponse.setContentType("application/json");

httpServletResponse.setStatus(HttpServletResponse.SC_OK);

try {

ObjectMapper mapper = new ObjectMapper();

mapper.writeValue(httpServletResponse.getOutputStream(), map);

} catch (Exception er) {

throw new ServletException();

}

}

}

success handler

public class TokenSuccessHandler  implements AuthenticationSuccessHandler {

private TokenService tokenService;

public TokenSuccessHandler(TokenService tokenService) {

this.tokenService = tokenService;

}

@Override

public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {

Map<String, Object> map = new HashMap<>();

map.put("code", 200);

map.put("success", true);

map.put("msg", "成功");

XxRequestToken xxRequestToken = tokenService.generateToken(authentication);

map.put("data", xxRequestToken);

httpServletResponse.setContentType("application/json");

httpServletResponse.setStatus(HttpServletResponse.SC_OK);

try {

XxUserDetails userDetails = SecurityUtil.getUserDetails(authentication);

if (userDetails != null) {

String ipAddr = IPUtil.getIpAddr(httpServletRequest);

SpringUtil.publishEvent(new LoginOkEvent(new LoginInfo(userDetails.getId(), ipAddr)));

}

ObjectMapper mapper = new ObjectMapper();

mapper.writeValue(httpServletResponse.getOutputStream(), map);

} catch (Exception e) {

throw new ServletException();

}

}

}

鉴权与授权

/**

* 校验 token

*/

@Slf4j

public class TokenCheckFilter extends GenericFilterBean {

private static final String AUTHORIZATION_HEADER = "Authorization";

private TokenService tokenService;

public TokenCheckFilter(TokenService tokenService) {

this.tokenService = tokenService;

}

@Override

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

HttpServletRequest req = (HttpServletRequest) servletRequest;

HttpServletResponse res = (HttpServletResponse) servletResponse;

String tokenKey = resolveToken(req);

String requestURI = req.getRequestURI();

boolean hasAuth = false;

if (StringUtils.hasText(tokenKey) && tokenService.validateToken(tokenKey)) {

Authentication authentication = tokenService.getAuthentication(tokenKey);

if (authentication != null) {

//authentication 中的 principle 可以转换为 XxUserDetails,供 SecurityUtil 提取权限信息

SecurityContextHolder.getContext().setAuthentication(authentication);

hasAuth = true;

}

}

if (requestURI.startsWith(SecurityConstant.LOGOUT_URL)) {

logout(hasAuth, tokenKey, res);

return;

}

filterChain.doFilter(servletRequest, servletResponse);

}

private String resolveToken(HttpServletRequest request) {

String bearerToken = request.getHeader(AUTHORIZATION_HEADER);

if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {

return bearerToken.substring(7);

}

return null;

}

private void logout(boolean isAuthenticated, String tokenKey, HttpServletResponse response) throws ServletException {

Map<String, Object> map = new HashMap<>();

if (isAuthenticated && tokenService.offline(tokenKey)) {

map.put("code", 200);

map.put("success", true);

map.put("msg", "注销成功");

} else {

map.put("code", 400);

map.put("success", false);

map.put("msg", "注销失败/已注销");

}

response.setContentType("application/json");

response.setStatus(HttpServletResponse.SC_OK);

try {

ObjectMapper mapper = new ObjectMapper();

mapper.writeValue(response.getOutputStream(), map);

} catch (Exception e) {

throw new ServletException();

}

}

}

 

以上是 springboot基于shiro/springsecurity实现自定义登录 的全部内容, 来源链接: utcz.com/z/515547.html

回到顶部