предисловие
Наиболее часто используемым методом в Интернете является схема UserDetailsService, в которой сложно реализовать несколько методов входа в систему. В этой статье используется схема Filter+Manager+Provider+Token для реализации нескольких методов входа в систему. Кодов слишком много, в этой статье показан только один из способов входа. Кратко опишите, что делают эти вещи:
- Фильтр отвечает за перехватывающие запросы и менеджер звонков
- Менеджер отвечает за управление несколькими провайдерами и выбор соответствующего провайдера для аутентификации.
- Провайдер отвечает за аутентификацию, проверку паролей учетных записей и т. д.
- Токен — это информация для аутентификации, включая пароль учетной записи, который можно определить самостоятельно.
Код фильтра
Сначала посмотрите на код фильтра
public class UserPasswordAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {
protected UserPasswordAuthenticationProcessingFilter() {
super("/login");//认证url
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
//根据前端参数判断是哪种登录类型,封装成对应方式的Token,提交给Manager
if("password".equals(request.getParameter("type"))){
String user = request.getParameter("username");
String password = request.getParameter("password");
System.out.println(user);
//把账号密码封装成token,传给manager认证
return getAuthenticationManager().authenticate(new UserPasswordAuthenticationToken(user,password));
}else{
//其他登录方式
return null;
}
}
}
Код токена
Суть токена заключается в двух методах построения: один используется до аутентификации, а другой — после аутентификации.
public class UserPasswordAuthenticationToken extends AbstractAuthenticationToken {
//自定义属性
private long id;
private long storeId;
private String isAdmin;
private String user;
private String password;
//一些get、set方法
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public long getId(){
return this.id;
}
public void setId(long id) {
this.id = id;
}
public long getStoreId() {
return storeId;
}
public void setStoreId(long storeId) {
this.storeId = storeId;
}
/**
* 认证时过滤器通过这个方法创建Token,传入前端的参数
* @param user
* @param password
*/
public UserPasswordAuthenticationToken(String user,String password){
super(null);
this.user = user;
this.password=password;
//关键:标记未认证
super.setAuthenticated(false);
}
/**
* 认证通过后Provider通过这个方法创建Token,传入自定义信息以及授权信息
* @param id
* @param storeId
* @param isAdmin
* @param authorities
*/
public UserPasswordAuthenticationToken(long id,Long storeId,String isAdmin,Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.id = id;
if(storeId==null){
this.storeId=0;
}else{
this.storeId=storeId;
}
this.isAdmin=isAdmin;
//关键:标记已认证
super.setAuthenticated(true);
}
//父类获取授权信息的两个方法,区别是啥不太清楚,但都可以返回自定义信息
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() {
return this.isAdmin;
}
}
Код провайдера
public class UserPasswordAuthentiactionProvider implements AuthenticationProvider {
@Autowired
private UserDao userDao;
/**
* 在此方法进行认证
* @param authentication
* @return
* @throws AuthenticationException
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//认证代码,认证通过返回认证对象,失败返回null
UserPasswordAuthenticationToken userPasswordAuthenticationToken = (UserPasswordAuthenticationToken)authentication;
if(userPasswordAuthenticationToken.getUser()==null || userPasswordAuthenticationToken.getPassword()==null){
return null;
}
User user=userDao.login(userPasswordAuthenticationToken);
if(user!=null){
//授予用户权限
String role;
if(user.getIsAdmin().equals("false")){
role="ROLE_USER";//此处的权限是固定格式,否则不能用:ROLE_+权限,大写
}else{
role="ROLE_ADMIN";//管理员权限
}
//返回认证后的Token
return new UserPasswordAuthenticationToken(user.getId(),user.getStoreId(),user.getIsAdmin(),
Arrays.asList(new SimpleGrantedAuthority(role)));
}
return null;
}
/**
* 此方法决定Provider能够处理哪些Token,此Provider只能处理密码登录方式的Token,这里也是多种登录方式的核心
* @param aClass
* @return
*/
@Override
public boolean supports(Class<?> aClass) {
//Manager传递token给provider,调用本方法判断该provider是否支持该token。不支持则尝试下一个filter
//本类支持的token类:UserPasswordAuthenticationToken
return (UserPasswordAuthenticationToken.class.isAssignableFrom(aClass));
}
}
Конфигурация безопасности
В основном это зависит от конфигурации ядра, которая отмечена в комментариях. Другие конечные точки входа, обработка сбоев и т. д. могут быть изменены в соответствии с вашими потребностями, и нет необходимости в том, чтобы они были точно такими же.
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//核心:provider配置
@Bean
UserPasswordAuthentiactionProvider userPasswordAuthentiactionProvider(){
return new UserPasswordAuthentiactionProvider();
}
//核心:filter配置
UserPasswordAuthenticationProcessingFilter userPasswordAuthenticationProcessingFilter(AuthenticationManager authenticationManager){
UserPasswordAuthenticationProcessingFilter userPasswordAuthenticationProcessingFilter = new UserPasswordAuthenticationProcessingFilter();
//为filter设置管理器
userPasswordAuthenticationProcessingFilter.setAuthenticationManager(authenticationManager);
//登录成功后跳转
userPasswordAuthenticationProcessingFilter.setAuthenticationSuccessHandler((httpServletRequest, httpServletResponse, authentication) -> {
httpServletResponse.sendRedirect("/");
});
userPasswordAuthenticationProcessingFilter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/getLogin?error"));
return userPasswordAuthenticationProcessingFilter;
}
//配置登录端点
@Bean
LoginUrlAuthenticationEntryPoint loginUrlAuthenticationEntryPoint(){
LoginUrlAuthenticationEntryPoint loginUrlAuthenticationEntryPoint = new LoginUrlAuthenticationEntryPoint
("/getLogin");
return loginUrlAuthenticationEntryPoint;
}
@Bean
AuthenticationEntryPoint authenticationEntryPoint(){
AuthenticationEntryPoint authenticationEntryPoint= (httpServletRequest, httpServletResponse, e) ->
{
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
ObjectMapper mapper = new ObjectMapper();
Message message=new Message();
out.write(mapper.writeValueAsString(message.error_login()));
out.flush();
out.close();
};
return authenticationEntryPoint;
}
protected void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().disable();//允许在iframe中加载页面
http.csrf().disable();//禁用csrf,否则post请求无法提交,只能通过模板渲染
http
.authorizeRequests()
//.antMatchers("/*/*").permitAll()
.antMatchers("/admin/**").authenticated()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").authenticated()
.antMatchers("/user/**").hasRole("USER")
//.anyRequest().authenticated()
.and()
.formLogin()
//此处写登录成功后的操作无效,userPasswordAuthenticationProcessingFilter()已接管此类设置
.loginPage("/getLogin")
.permitAll()
.and()
.logout()
.permitAll()
.and()
.exceptionHandling()
//已登录用户无权访问时的登录端点
.accessDeniedHandler(((httpServletRequest, httpServletResponse, e) -> httpServletResponse.sendRedirect("/getLogin?forbidden")))
//accessDeniedPage("/getLogin?forbidden")//无权访问返回该页面
//配置未登录用户无权访问时的登录端点
.authenticationEntryPoint(authenticationEntryPoint());
//核心:添加过滤器,注意先后顺序
http.addFilterBefore(userPasswordAuthenticationProcessingFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
}
//核心:配置管理器
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
//在管理器中添加provider
auth.authenticationProvider(userPasswordAuthentiactionProvider());
}
}