در حین نوشتن درخواستم، با کمبود مقالات واضح در مورد نحوه وادار کردن کاربر به ثبت نام هم از طریق ایمیل و هم از طریق شبکه های اجتماعی مواجه شدم. آموزش های خوبی در مورد راه اندازی فرم ورود کلاسیک وجود داشت. آموزش های خوبی در OAuth2 وجود داشت . اطلاعات جنایی کمی در مورد نحوه ترکیب این دو روش وجود داشت. در طول فرآیند جستجو، ما توانستیم به یک راه حل قابل اجرا برسیم. ادعا نمی کند که حقیقت نهایی است، اما کارکرد خود را انجام می دهد. در این مقاله نحوه پیادهسازی سرویس ذخیرهسازی یادداشت با پیکربندی مشابه Spring Security را از ابتدا نشان خواهم داد. توجه: خوب است که خواننده حداقل چند آموزش در مورد Spring را مرور کرده باشد، زیرا توجه فقط بر روی Spring Security متمرکز خواهد شد، بدون توضیحات دقیق در مورد مخازن، کنترلرها و غیره. در غیر این صورت، یک مقاله نسبتاً بزرگ به نظر می رسد. غول پیکر باشد محتوا
می بینیم که یک کاربر جدید در پایگاه داده ظاهر شده است. رمز عبور رمزگذاری شده است.
یادداشت ها در پایگاه داده ذخیره می شوند.
می بینیم که پروژه با موفقیت راه اندازی و اجرا می شود. برای خوشبختی کامل فقط به توانایی ورود از طریق شبکه های اجتماعی نیاز داریم. خوب، بیایید شروع کنیم!
اکنون هم User و هم OAuth2Authentication می توانند به عنوان Principal عمل کنند. از آنجایی که ما در UserService از قبل بارگیری کاربر را از طریق داده های Google در نظر گرفتیم، برنامه برای هر دو نوع کاربر کار خواهد کرد. ما کنترل کننده صفحه اصلی پروژه را تغییر می دهیم تا کاربرانی که با استفاده از OAuth2 وارد شده اند را به صفحه یادداشت ها هدایت کند.
کاربر با موفقیت هم از طریق فرم معمولی و هم از طریق حساب Google وارد سیستم می شود. این همان چیزی است که ما می خواستیم! امیدوارم این مقاله نکاتی را در مورد ایجاد یک برنامه وب، ایمن سازی آن با Spring Security و ترکیب روش های مختلف ورود روشن کرده باشد. با کد کامل پروژه می توانید
- ایجاد یک پروژه
- ایجاد موجودیت ها و منطق برنامه
- پیکربندی امنیت Spring برای ورود کلاسیک
- راه اندازی OAuth2 با استفاده از Google به عنوان مثال در Spring Security
- پیکربندی فیلتر و application.properties
- نکات مهم ثبت برنامه در Google Cloud Platform
- CustomUserInfoTokenServices
- راه اندازی نهایی پروژه
ایجاد یک پروژه
ما به start.spring.io می رویم و اساس پروژه را تشکیل می دهیم:- وب - راه اندازی یک برنامه کاربردی در Tomcat داخلی، نگاشت آدرس و موارد مشابه؛
- JPA - اتصال به پایگاه داده؛
- Mustache یک موتور قالب است که برای تولید صفحات وب استفاده می شود.
- امنیت - حفاظت از برنامه. این همان چیزی است که این مقاله برای آن ایجاد شده است.
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
پیکربندی application.properties در حال حاضر به شرح زیر است:
spring.datasource.url=jdbc:mysql://localhost:3306/springsectut?createDatabaseIfNotExist=true&useSSL=false&autoReconnect=true&useLegacyDatetimeCode=false&serverTimezone=UTC&useUnicode=yes&characterEncoding=UTF-8
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=yourUsername
spring.datasource.password=yourPassword
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.connection.characterEncoding=utf-8
spring.jpa.properties.connection.CharSet=utf-8
spring.jpa.properties.connection.useUnicode=true
spring.mustache.expose-request-attributes=true
ایجاد موجودیت ها و منطق برنامه
موجودیت ها
بیایید بسته ای ایجاد کنیمentities
که موجودیت های پایگاه داده را در آن قرار دهیم. کاربر توسط کلاسی توصیف میشود User
که رابط را پیادهسازی میکند UserDetails
، که برای پیکربندی Spring Security مورد نیاز است. کاربر یک شناسه، نام کاربری (این ایمیل است)، رمز عبور، نام، نقش، پرچم فعالیت، نام حساب Google و ایمیل ( googleName
و googleUsername
) خواهد داشت.
@Entity
@Table(name = "user")
public class User implements UserDetails
{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String username;
private String password;
private String name;
private boolean active;
private String googleName;
private String googleUsername;
@ElementCollection(targetClass = Role.class, fetch = FetchType.EAGER)
@CollectionTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"))
@Enumerated(EnumType.STRING)
private Set<Role> roles;
//Геттеры, сеттеры, toString(), equals(), hashcode(), имплементация UserDetails
}
نقش های کاربر برای تنظیم دسترسی در Spring Security استفاده می شود. برنامه ما فقط از یک نقش استفاده می کند:
public enum Role implements GrantedAuthority
{
USER;
@Override
public String getAuthority()
{
return name();
}
}
بیایید یک کلاس یادداشت با شناسه، عنوان یادداشت، بدنه یادداشت و شناسه کاربری که به آن تعلق دارد ایجاد کنیم:
@Entity
@Table(name = "note")
public class Note
{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private String note;
private Long userId;
//Геттеры, сеттеры, toString(), equals(), hashcode()
}
مخازن
برای ذخیره موجودیت ها در پایگاه داده، به مخازنی نیاز داریم که همه کارهای کثیف را برای ما انجام دهند. بیایید یک بسته ایجاد کنیمrepos
، در آن رابط هایی را ایجاد خواهیم کرد که از اینترفیس UserRepo
به ارث رسیده اند . NoteRepo
JpaRepository<Entity, Id>
@Service
@Repository
public interface UserRepo extends JpaRepository<User, Long>
{}
@Service
@Repository
public interface NoteRepo extends JpaRepository<Note, Long>
{
List<Note> findByUserId(Long userId);
}
کنترل کننده ها
سرویس یادداشت های ما دارای صفحات زیر خواهد بود:- صفحه اصلی;
- ثبت؛
- ورود؛
- لیست یادداشت های کاربر
controllers
حاوی یک کلاس IndexController
حاوی نگاشت معمول صفحه اصلی ایجاد کنیم. کلاس RegistrationController
مسئولیت ثبت نام کاربر را بر عهده دارد. Post-mapping داده ها را از فرم می گیرد، کاربر را در پایگاه داده ذخیره می کند و به صفحه ورود هدایت می کند. PasswordEncoder
بعدا شرح داده خواهد شد. برای رمزگذاری رمزهای عبور استفاده می شود.
@Controller
public class RegistrationController
{
@Autowired
private UserRepo userRepo;
@Autowired
private PasswordEncoder passwordEncoder;
@GetMapping("/registration")
public String registration()
{
return "registration";
}
@PostMapping("/registration")
public String addUser(String name, String username, String password)
{
User user = new User();
user.setName(name);
user.setUsername(username);
user.setPassword(passwordEncoder.encode(password));
user.setActive(true);
user.setRoles(Collections.singleton(Role.USER));
userRepo.save(user);
return "redirect:/login";
}
کنترل کننده مسئول صفحه لیست یادداشت ها در حال حاضر دارای عملکرد ساده شده است که پس از اجرای Spring Security پیچیده تر می شود.
@Controller
public class NoteController
{
@Autowired
private NoteRepo noteRepo;
@GetMapping("/notes")
public String notes(Model model)
{
List<Note> notes = noteRepo.findAll();
model.addAttribute("notes", notes);
return "notes";
}
@PostMapping("/addnote")
public String addNote(String title, String note)
{
Note newNote = new Note();
newNote.setTitle(title);
newNote.setNote(note);
noteRepo.save(newNote);
return "redirect:/notes";
}
}
ما کنترل کننده ای برای صفحه ورود نخواهیم نوشت زیرا در امنیت Spring استفاده می شود. در عوض، ما به یک پیکربندی خاص نیاز خواهیم داشت. طبق معمول، بیایید بسته دیگری ایجاد کنیم، آن را فراخوانی کنیم config
و کلاس را در آنجا قرار دهیم MvcConfig
. وقتی پیکربندی Spring Security را می نویسیم، وقتی از "/login" استفاده می کنیم، متوجه می شود که به کدام صفحه اشاره می کنیم.
@Configuration
public class MvcConfig implements WebMvcConfigurer
{
public void addViewControllers(ViewControllerRegistry registry)
{
registry.addViewController("/login").setViewName("login");
registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
}
}
صفحات
من از موتور قالب Mustache برای ایجاد صفحات استفاده می کنم . شما می توانید یکی دیگر را پیاده سازی کنید، مهم نیست. یک فایل meta.mustache برای اطلاعات متا ایجاد شده است که در تمام صفحات استفاده می شود. همچنین شامل Bootstrap می شود تا صفحات پروژه ما زیباتر به نظر برسند. صفحات در دایرکتوری "src/main/resources/templates" ایجاد می شوند. فایل ها دارای پسوند سبیل هستند. قرار دادن کد html به طور مستقیم در مقاله آن را بیش از حد بزرگ می کند، بنابراین در اینجا پیوندی به پوشه الگوها در مخزن GitHub پروژه وجود دارد .پیکربندی امنیت Spring برای ورود کلاسیک
Spring Security به ما کمک می کند تا از برنامه و منابع آن در برابر دسترسی غیرمجاز محافظت کنیم. ما یک پیکربندی کاری مختصر در کلاسی کهSecurityConfig
از آن به ارث رسیده است ایجاد خواهیم کرد WebSecurityConfigurerAdapter
که آن را در بسته قرار می دهیم config
. بیایید آن را با حاشیهنویسی EnableWebSecurity@ علامتگذاری کنیم، که پشتیبانی Spring Security را فعال میکند، و حاشیهنویسی @Configuration، که نشان میدهد این کلاس دارای تنظیماتی است. توجه: pom.xml پیکربندی شده به صورت خودکار حاوی نسخه مولفه 2.1.4.RELEASE والد Spring Boot بود که مانع از اجرای امنیت به روش تعیین شده می شد. برای جلوگیری از درگیری در پروژه، توصیه می شود نسخه را به 2.0.1.RELEASE تغییر دهید.
پیکربندی پایه SecurityConfig
پیکربندی ما قادر خواهد بود:-
رمزگذاری رمزهای عبور با استفاده از
BCryptPasswordEncoder
:@Autowired private PasswordEncoder passwordEncoder; @Bean PasswordEncoder passwordEncoder() { PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); return passwordEncoder; }
-
با استفاده از یک ارائه دهنده احراز هویت نوشته شده ویژه وارد شوید:
@Autowired private AuthProvider authProvider; @Override protected void configure(AuthenticationManagerBuilder auth) { auth.authenticationProvider(authProvider); }
-
به کاربران ناشناس اجازه دسترسی به صفحه اصلی، صفحات ثبت نام و ورود به سیستم را بدهید. تمام درخواست های دیگر باید توسط کاربرانی که وارد سیستم شده اند انجام شود. بیایید «/login» را که قبلاً توضیح داده شد به عنوان صفحه ورود اختصاص دهیم. در صورت موفقیت آمیز بودن ورود، کاربر به صفحه ای با لیستی از یادداشت ها هدایت می شود و در صورت بروز خطا، کاربر در صفحه ورود باقی می ماند. پس از خروج موفقیت آمیز، کاربر به صفحه اصلی منتقل می شود.
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/resources/**", "/", "/login**", "/registration").permitAll() .anyRequest().authenticated() .and().formLogin().loginPage("/login") .defaultSuccessUrl("/notes").failureUrl("/login?error").permitAll() .and().logout().logoutSuccessUrl("/").permitAll(); }
ورود کاربر سفارشی
یک خود نوشتهAuthProvider
به کاربر این امکان را می دهد که نه تنها از طریق ایمیل، بلکه با نام کاربری وارد سیستم شود.
@Component
public class AuthProvider implements AuthenticationProvider
{
@Autowired
private UserService userService;
@Autowired
private PasswordEncoder passwordEncoder;
public Authentication authenticate(Authentication authentication) throws AuthenticationException
{
String username = authentication.getName();
String password = (String) authentication.getCredentials();
User user = (User) userService.loadUserByUsername(username);
if(user != null && (user.getUsername().equals(username) || user.getName().equals(username)))
{
if(!passwordEncoder.matches(password, user.getPassword()))
{
throw new BadCredentialsException("Wrong password");
}
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
return new UsernamePasswordAuthenticationToken(user, password, authorities);
}
else
throw new BadCredentialsException("Username not found");
}
public boolean supports(Class<?> arg)
{
return true;
}
}
همانطور که ممکن است متوجه شده باشید، کلاسی UserService
که در بسته قرار دارد مسئول بارگیری کاربر است services
. در مورد ما، کاربر را نه تنها از طریق فیلد username
، مانند پیاده سازی داخلی، بلکه با نام کاربری، نام حساب Google و ایمیل حساب Google نیز جستجو می کند. دو روش آخر هنگام پیاده سازی ورود از طریق OAuth2 برای ما مفید خواهد بود. در اینجا کلاس به صورت مختصر ارائه شده است.
@Service
public class UserService implements UserDetailsService
{
@Autowired
private UserRepo userRepo;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
User userFindByUsername = userRepo.findByUsername(username);
//Остальные поиски
if(userFindByUsername != null)
{
return userFindByUsername;
}
//Остальные проверки
return null;
}
}
توجه: فراموش نکنید که روش های لازم را در UserRepo
!
بیایید کنترلر را بهبود ببخشیم
ما امنیت Spring را پیکربندی کرده ایم. اکنون زمان آن است که از این ویژگی در کنترلر یادداشت های خود استفاده کنید. اکنون هر نقشهبرداری یک پارامتر اصلی اضافی را میپذیرد که توسط آن سعی میکند کاربر را پیدا کند. چرا نمی توانم مستقیماً کلاس را تزریق کنمUser
؟ سپس در هنگام نوشتن ورود از طریق شبکه های اجتماعی به دلیل عدم تطابق انواع کاربر، درگیری ایجاد می شود. ما از قبل انعطاف لازم را ارائه می دهیم. کد کنترل کننده یادداشت های ما اکنون به این صورت است:
@GetMapping("/notes")
public String notes(Principal principal, Model model)
{
User user = (User) userService.loadUserByUsername(principal.getName());
List<Note> notes = noteRepo.findByUserId(user.getId());
model.addAttribute("notes", notes);
model.addAttribute("user", user);
return "notes";
}
@PostMapping("/addnote")
public String addNote(Principal principal, String title, String note)
{
User user = (User) userService.loadUserByUsername(principal.getName());
Note newNote = new Note();
newNote.setTitle(title);
newNote.setNote(note);
newNote.setUserId(user.getId());
noteRepo.save(newNote);
return "redirect:/notes";
}
توجه: پروژه دارای حفاظت CSRF به طور پیش فرض فعال است ، بنابراین یا آن را برای خودتان غیرفعال کنید (http.csrf().disable())، یا فراموش نکنید، به عنوان نویسنده مقاله، یک فیلد مخفی را با یک نشانه csrf اضافه کنید. به همه درخواست های پست
راه اندازی
تلاش می کنیم پروژه را راه اندازی کنیم.راه اندازی OAuth2 با استفاده از Google به عنوان مثال در Spring Security
هنگام اجرای OAuth2، من به این آموزش رسمی از Spring تکیه کردم . برای پشتیبانی از OAuth2، کتابخانه زیر را به pom.xml اضافه کنید:<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
بیایید پیکربندی Spring Security خود را در قسمت تغییر دهیم SecurityConfig
. ابتدا، اجازه دهید حاشیه نویسی @EnableOAuth2Client را اضافه کنیم. این به طور خودکار آنچه را که برای ورود به سیستم از طریق شبکه های اجتماعی نیاز دارید، نشان می دهد.
پیکربندی فیلتر و application.properties
بیایید OAuth2ClientContext را برای استفاده در پیکربندی امنیتی خود تزریق کنیم.@Autowired
private OAuth2ClientContext oAuth2ClientContext;
OAuth2ClientContext هنگام ایجاد فیلتری استفاده می شود که درخواست ورود به سیستم اجتماعی کاربر را تأیید می کند. این فیلتر به لطف حاشیه نویسی @EnableOAuth2Client در دسترس است. تنها کاری که باید انجام دهیم این است که آن را به ترتیب صحیح، قبل از فیلتر اصلی Spring Security فراخوانی کنیم. تنها در این صورت است که میتوانیم در طول فرآیند ورود به سیستم با OAuth2، تغییر مسیرها را دریافت کنیم. برای انجام این کار، از - استفاده می کنیم FilterRegistrationBean
که در آن اولویت فیلتر خود را 100- می کنیم.
@Bean
public FilterRegistrationBean oAuth2ClientFilterRegistration(OAuth2ClientContextFilter oAuth2ClientContextFilter)
{
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(oAuth2ClientContextFilter);
registration.setOrder(-100);
return registration;
}
private Filter ssoFilter()
{
OAuth2ClientAuthenticationProcessingFilter googleFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/google");
OAuth2RestTemplate googleTemplate = new OAuth2RestTemplate(google(), oAuth2ClientContext);
googleFilter.setRestTemplate(googleTemplate);
CustomUserInfoTokenServices tokenServices = new CustomUserInfoTokenServices(googleResource().getUserInfoUri(), google().getClientId());
tokenServices.setRestTemplate(googleTemplate);
googleFilter.setTokenServices(tokenServices);
tokenServices.setUserRepo(userRepo);
tokenServices.setPasswordEncoder(passwordEncoder);
return googleFilter;
}
همچنین باید یک فیلتر جدید به تابع configure (HttpSecurity http) اضافه کنید:
http.addFilterBefore(ssoFilter(), UsernamePasswordAuthenticationFilter.class);
فیلتر همچنین باید بداند که مشتری از طریق Google ثبت نام کرده است. حاشیه نویسی @ConfigurationProperties مشخص می کند که کدام ویژگی های پیکربندی را در application.properties جستجو کنیم.
@Bean
@ConfigurationProperties("google.client")
public AuthorizationCodeResourceDetails google()
{
return new AuthorizationCodeResourceDetails();
}
برای تکمیل احراز هویت، باید نقطه پایانی اطلاعات کاربر Google را مشخص کنید:
@Bean
@ConfigurationProperties("google.resource")
public ResourceServerProperties googleResource()
{
return new ResourceServerProperties();
}
پس از ثبت برنامه خود در Google Cloud Platform ، ویژگی هایی را با پیشوندهای مناسب به application.properties اضافه می کنیم:
google.client.clientId=yourClientId
google.client.clientSecret=yourClientSecret
google.client.accessTokenUri=https://www.googleapis.com/oauth2/v4/token
google.client.userAuthorizationUri=https://accounts.google.com/o/oauth2/v2/auth
google.client.clientAuthenticationScheme=form
google.client.scope=openid,email,profile
google.resource.userInfoUri=https://www.googleapis.com/oauth2/v3/userinfo
google.resource.preferTokenInfo=true
نکات مهم ثبت برنامه در Google Cloud Platform
مسیر: APIها و خدمات -> پنجره درخواست دسترسی OAuth اعتبارنامه:- نام برنامه: فرم ورود به سیستم و آموزش OAuth2
- آدرس ایمیل پشتیبانی: ایمیل شما
- محدوده برای Google API: ایمیل، نمایه، openid
- دامنه های مجاز: me.org
- لینک صفحه اصلی اپلیکیشن: http://me.org:8080
- پیوند به سیاست حفظ حریم خصوصی برنامه: http://me.org:8080
- پیوند به شرایط استفاده از برنامه: http://me.org:8080
- نوع: وب اپلیکیشن
- عنوان: فرم ورود به سیستم و آموزش OAuth2
- منابع مجاز جاوا اسکریپت: http://me.org, http://me.org:8080
- URIهای تغییر مسیر مجاز: http://me.org:8080/login, http://me.org:8080/login/google
CustomUserInfoTokenServices
آیا متوجه کلمه Custom در توضیحات عملکرد فیلتر شده اید؟ کلاسCustomUserInfoTokenServices
. بله، ما کلاس خود را با بلک جک و قابلیت ذخیره کاربر در پایگاه داده ایجاد خواهیم کرد! با استفاده از میانبر صفحه کلید Ctrl-N در IntelliJ IDEA، می توانید نحوه UserInfoTokenServices
اجرای پیش فرض را بیابید و ببینید. بیایید کد آن را در کلاس جدید ایجاد شده کپی کنیم CustomUserInfoTokenServices
. بیشتر آن را می توان بدون تغییر رها کرد. قبل از تغییر منطق توابع، فیلدهای خصوصی کلاس را اضافه می UserRepo
کنیم PasswordEncoder
. بیایید تنظیم کننده برای آنها ایجاد کنیم. بیایید @Autowired UserRepo userRepo را به کلاس SecurityConfig اضافه کنیم. ما به نحوه ناپدید شدن نشانگر خطا در روش ایجاد فیلتر نگاه می کنیم و خوشحال می شویم. چرا @Autowired نمی تواند مستقیماً در CustomUserInfoTokenServices اعمال شود؟ زیرا این کلاس وابستگی را دریافت نمی کند، زیرا خود با هیچ حاشیه نویسی Spring علامت گذاری نشده است و سازنده آن به صراحت هنگام اعلان فیلتر ایجاد می شود. بر این اساس، مکانیسم DI اسپرینگ از آن اطلاعی ندارد. اگر @Autowired را روی هر چیزی در این کلاس حاشیه نویسی کنیم، هنگام استفاده یک NullPointerException دریافت خواهیم کرد. اما از طریق تنظیم کننده های صریح همه چیز بسیار خوب کار می کند. پس از پیاده سازی اجزای لازم، شی اصلی مورد علاقه تابع loadAuthentication می شود که در آن Map<String, Object> با اطلاعات کاربر بازیابی می شود. در این پروژه بود که من ذخیره کاربری که از طریق یک شبکه اجتماعی وارد شده بود را در پایگاه داده پیاده سازی کردم. از آنجایی که ما از یک حساب Google به عنوان ارائهدهنده OAuth2 استفاده میکنیم، بررسی میکنیم که آیا نقشه حاوی فیلد «sub» است که برای Google معمول است یا خیر. در صورت وجود به این معنی است که اطلاعات مربوط به کاربر به درستی دریافت شده است. ما یک کاربر جدید ایجاد می کنیم و آن را در پایگاه داده ذخیره می کنیم.
@Override
public OAuth2Authentication loadAuthentication(String accessToken)
throws AuthenticationException, InvalidTokenException
{
Map<String, Object> map = getMap(this.userInfoEndpointUrl, accessToken);
if(map.containsKey("sub"))
{
String googleName = (String) map.get("name");
String googleUsername = (String) map.get("email");
User user = userRepo.findByGoogleUsername(googleUsername);
if(user == null)
{
user = new User();
user.setActive(true);
user.setRoles(Collections.singleton(Role.USER));
}
user.setName(googleName);
user.setUsername(googleUsername);
user.setGoogleName(googleName);
user.setGoogleUsername(googleUsername);
user.setPassword(passwordEncoder.encode("oauth2user"));
userRepo.save(user);
}
if (map.containsKey("error"))
{
this.logger.debug("userinfo returned error: " + map.get("error"));
throw new InvalidTokenException(accessToken);
}
return extractAuthentication(map);
}
هنگام استفاده از چندین ارائه دهنده، می توانید گزینه های مختلفی را در یک CustomUserInfoTokenServices مشخص کنید و کلاس های مختلفی از خدمات مشابه را در روش اعلام فیلتر ثبت کنید.
@GetMapping("/")
public String index(Principal principal)
{
if(principal != null)
{
return "redirect:/notes";
}
return "index";
}
GO TO FULL VERSION