security 主要涉及模块
security 原生类 注释 (自定义类需)
AuthenticationFilter 身份验证筛选器 继承类 UsernamePasswordAuthenticationFilter
AuthenticationManagerBuilder 身份验证管理器生成器 实现接口 UserDetailsService / UserDetails(通过两个类实现接口)
[FilterSecurityInterceptor]
AccessDecisionManager 访问决策管理器 实现接口 FilterInvocationSecurityMetadataSource
SecurityMetadataSource 安全元数据源 实现接口 AccessDecisionManager
security
用户
UsernamePass
UserDetailsService
UserDetails
FilterInvocation...
AccessDecision
user(请求)
UsernamePasswordAuthenticationFilter
UserDetailsService
检验(用户名,密码,验证码)
username
Authorities
设置用户权限
authorities.add(new
SimpleGrantedAuthority
(role.getName()))
登录失败信息(验证码,用户,密码错,过期等)
request
FilterInvocationSecurity
MetadataSource
获取能访问该请求的
权限 return
Collection<ConfigAttribute>
authentication,configAttributes
AccessDecis
ionManager校
验用户是否携带(aut
hentication
)路径所需要(conf
igAttribute
s)的权限
fail:权限不足或别的错误提示 success: 渲染对应的页面
用户
UsernamePass
UserDetailsService
UserDetails
FilterInvocation...
AccessDecision
Security
securityConfig
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
MyUserService myUserService
;
@Autowired
CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource
;
@Autowired
CustomUrlDecisionManager customUrlDecisionManager
;
@Bean
PasswordEncoder
passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth
) throws Exception
{
auth
.userDetailsService(myUserService
);
}
@Override
public void configure(WebSecurity web
) throws Exception
{
web
.ignoring().antMatchers("/css/**", "/js/**", "/index.html", "/img/**", "/fonts/**", "/favicon.ico", "/verifyCode","/verifyCode1");
}
@Bean
LoginFilter
loginFilter() throws Exception
{
LoginFilter loginFilter
= new LoginFilter();
loginFilter
.setAuthenticationSuccessHandler((request
, response
, authentication
) -> {
response
.setContentType("application/json;charset=utf-8");
PrintWriter out
= response
.getWriter();
Hr hr
= (Hr
) authentication
.getPrincipal();
hr
.setPassword(null);
RespBean ok
= RespBean
.ok("登录成功!", hr
);
String s
= new ObjectMapper().writeValueAsString(ok
);
out
.write(s
);
out
.flush();
out
.close();
}
);
loginFilter
.setAuthenticationFailureHandler((request
, response
, exception
) -> {
response
.setContentType("application/json;charset=utf-8");
PrintWriter out
= response
.getWriter();
RespBean respBean
= RespBean
.error(exception
.getMessage());
if (exception
instanceof LockedException) {
respBean
.setMsg("账户被锁定,请联系管理员!");
} else if (exception
instanceof CredentialsExpiredException) {
respBean
.setMsg("密码过期,请联系管理员!");
} else if (exception
instanceof AccountExpiredException) {
respBean
.setMsg("账户过期,请联系管理员!");
} else if (exception
instanceof DisabledException) {
respBean
.setMsg("账户被禁用,请联系管理员!");
} else if (exception
instanceof BadCredentialsException) {
respBean
.setMsg("用户名或者密码输入错误,请重新输入!");
}
out
.write(new ObjectMapper().writeValueAsString(respBean
));
out
.flush();
out
.close();
}
);
loginFilter
.setAuthenticationManager(authenticationManagerBean());
loginFilter
.setFilterProcessesUrl("/doLogin");
ConcurrentSessionControlAuthenticationStrategy sessionStrategy
= new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());
sessionStrategy
.setMaximumSessions(1);
loginFilter
.setSessionAuthenticationStrategy(sessionStrategy
);
return loginFilter
;
}
@Bean
SessionRegistryImpl
sessionRegistry() {
return new SessionRegistryImpl();
}
@Override
protected void configure(HttpSecurity http
) throws Exception
{
http
.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor
>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object
) {
object
.setAccessDecisionManager(customUrlDecisionManager
);
object
.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource
);
return object
;
}
})
.and()
.logout()
.logoutSuccessHandler((req
, resp
, authentication
) -> {
resp
.setContentType("application/json;charset=utf-8");
PrintWriter out
= resp
.getWriter();
out
.write(new ObjectMapper().writeValueAsString(RespBean
.ok("注销成功!")));
out
.flush();
out
.close();
}
)
.permitAll()
.and()
.csrf().disable().exceptionHandling()
.authenticationEntryPoint((req
, resp
, authException
) -> {
resp
.setContentType("application/json;charset=utf-8");
resp
.setStatus(401);
PrintWriter out
= resp
.getWriter();
RespBean respBean
= RespBean
.error("访问失败!");
if (authException
instanceof InsufficientAuthenticationException) {
respBean
.setMsg("请求失败,请联系管理员!");
}
out
.write(new ObjectMapper().writeValueAsString(respBean
));
out
.flush();
out
.close();
}
);
http
.addFilterAt(new ConcurrentSessionFilter(sessionRegistry(), event
-> {
HttpServletResponse resp
= event
.getResponse();
resp
.setContentType("application/json;charset=utf-8");
resp
.setStatus(401);
PrintWriter out
= resp
.getWriter();
out
.write(new ObjectMapper().writeValueAsString(RespBean
.error("您已在另一台设备登录,本次登录已下线!")));
out
.flush();
out
.close();
}), ConcurrentSessionFilter
.class);
http
.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter
.class);
}
}
UsernamePasswordAuthenticationFilter
只在用户初次登录时访问一次;进行验证码的校验
public class LoginFilter extends UsernamePasswordAuthenticationFilter {
@Autowired
SessionRegistry sessionRegistry
;
@Override
public Authentication
attemptAuthentication(HttpServletRequest request
, HttpServletResponse response
) throws AuthenticationException
{
if (!request
.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request
.getMethod());
}
String verify_code
= (String
) request
.getSession().getAttribute("verify_code");
if (request
.getContentType().contains(MediaType
.APPLICATION_JSON_VALUE) || request
.getContentType().contains(MediaType
.APPLICATION_JSON_UTF8_VALUE)) {
Map
<String
, String
> loginData
= new HashMap<>();
try {
loginData
= new ObjectMapper().readValue(request
.getInputStream(), Map
.class);
} catch (IOException e
) {
}finally {
String code
= loginData
.get("code");
checkCode(response
, code
, verify_code
);
}
String username
= loginData
.get(getUsernameParameter());
String password
= loginData
.get(getPasswordParameter());
if (username
== null) {
username
= "";
}
if (password
== null) {
password
= "";
}
username
= username
.trim();
UsernamePasswordAuthenticationToken authRequest
= new UsernamePasswordAuthenticationToken(
username
, password
);
setDetails(request
, authRequest
);
Hr principal
= new Hr();
principal
.setUsername(username
);
sessionRegistry
.registerNewSession(request
.getSession(true).getId(), principal
);
return this.getAuthenticationManager().authenticate(authRequest
);
} else {
checkCode(response
, request
.getParameter("code"), verify_code
);
return super.attemptAuthentication(request
, response
);
}
}
public void checkCode(HttpServletResponse resp
, String code
, String verify_code
) {
if (code
== null || verify_code
== null || "".equals(code
) || !verify_code
.toLowerCase().equals(code
.toLowerCase())) {
throw new AuthenticationServiceException("验证码不正确");
}
}
}
MyUserService
只在用户初次登录时访问一次;进行检测用户存在否;同时给用户注入权限。
public class MyUserService implements UserDetailsService {
@Override
public UserDetails
loadUserByUsername(String username
) throws UsernameNotFoundException
{
User user
= UserMapper
.loadUserByUsername(username
);
if (user
== null
) {
throw new UsernameNotFoundException("用户名不存在!");
}
user
.setRoles(userMapper
.getHrRolesById(user
.getId()));
return user
;
}
}
User
只在用户初次登录时访问一次;将用户所具备的权限 注册到security中交由容器管理。
public class User implements UserDetails {
@Override
public boolean equals(Object o
) {
if (this == o
) return true;
if (o
== null
|| getClass() != o
.getClass()) return false;
User user
= (User
) o
;
return Objects
.equals(username
, user
.username
);
}
@Override
public int hashCode() {
return Objects
.hash(username
);
}
public List
<Role> getRoles() {
return roles
;
}
public void setRoles(List
<Role> roles
) {
this.roles
= roles
;
}
@Override
@JsonIgnore
public Collection
<? extends GrantedAuthority> getAuthorities() {
List
<SimpleGrantedAuthority> authorities
= new ArrayList<>(roles
.size());
for (Role role
: roles
) {
authorities
.add(new SimpleGrantedAuthority(role
.getName()));
}
return authorities
;
}
}
FilterInvocationSecurityMetadataSource
每次各种请求都会进行拦截;检测出此次请求所需要的权限。
@Component
public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
@Autowired
MenuService menuService
;
AntPathMatcher antPathMatcher
= new AntPathMatcher();
@Override
public Collection
<ConfigAttribute> getAttributes(Object object
) throws IllegalArgumentException
{
String requestUrl
= ((FilterInvocation
) object
).getRequestUrl();
List
<Menu> menus
= menuService
.getAllMenusWithRole();
for (Menu menu
: menus
) {
if (antPathMatcher
.match(menu
.getUrl(), requestUrl
)) {
List
<Role> roles
= menu
.getRoles();
String
[] str
= new String[roles
.size()];
for (int i
= 0; i
< roles
.size(); i
++) {
str
[i
] = roles
.get(i
).getName();
}
return SecurityConfig
.createList(str
);
}
}
return SecurityConfig
.createList("ROLE_LOGIN");
}
@Override
public Collection
<ConfigAttribute> getAllConfigAttributes() {
return null
;
}
@Override
public boolean supports(Class
<?> clazz
) {
return true;
}
}
AccessDecisionManager
当解析出此次请求所需要的条件后,根据所需的权限校验当前用户是否携带有此次请求所需要的权限 从而实现权限管理。
@Component
public class CustomUrlDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication
, Object object
, Collection
<ConfigAttribute> configAttributes
) throws AccessDeniedException
, InsufficientAuthenticationException
{
for (ConfigAttribute configAttribute
: configAttributes
) {
String needRole
= configAttribute
.getAttribute();
if ("ROLE_LOGIN".equals(needRole
)) {
if (authentication
instanceof AnonymousAuthenticationToken) {
throw new AccessDeniedException("尚未登录,请登录!");
}else {
return;
}
}
Collection
<? extends GrantedAuthority> authorities
= authentication
.getAuthorities();
for (GrantedAuthority authority
: authorities
) {
if (authority
.getAuthority().equals(needRole
)) {
return;
}
}
}
throw new AccessDeniedException("权限不足,请联系管理员!");
}
@Override
public boolean supports(ConfigAttribute attribute
) {
return true;
}
@Override
public boolean supports(Class
<?> clazz
) {
return true;
}
}